2011-05-17 21 views
27

Tengo una tabla, llamémosla "foos", con casi 6 millones de registros en ella. Estoy ejecutando la siguiente consulta:Consulta PostgreSQL extremadamente lenta con cláusulas ORDER y LIMIT

SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0; 

Esta consulta lleva mucho tiempo en ejecutarse (Rails se agota mientras la ejecuta). Hay un índice en todas las identificaciones en cuestión. La parte curiosa es, si elimino la cláusula ORDER BY o la cláusula LIMIT, se ejecuta casi instantáneamente.

Supongo que la presencia de ORDER BY y LIMIT hace que PostgreSQL tome algunas malas decisiones en la planificación de consultas. Alguien tiene alguna idea de como arreglar esto?

En caso de que ayuda, aquí es el EXPLAIN para los 3 casos:

//////// Both ORDER and LIMIT 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0; 
                QUERY PLAN              
-------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..16663.44 rows=5 width=663) 
    -> Nested Loop (cost=0.00..25355084.05 rows=7608 width=663) 
     Join Filter: (foos.bar_id = bars.id) 
     -> Index Scan Backward using foos_pkey on foos (cost=0.00..11804133.33 rows=4963477 width=663) 
       Filter: (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text)) 
     -> Materialize (cost=0.00..658.96 rows=182 width=4) 
       -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
        Index Cond: (baz_id = 13266) 
(8 rows) 

//////// Just LIMIT 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
LIMIT 5 OFFSET 0; 
                   QUERY PLAN                
--------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..22.21 rows=5 width=663) 
    -> Nested Loop (cost=0.00..33788.21 rows=7608 width=663) 
     -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
       Index Cond: (baz_id = 13266) 
     -> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663) 
       Index Cond: (foos.bar_id = bars.id) 
       Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text)) 
(7 rows) 

//////// Just ORDER 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC; 
                   QUERY PLAN                
--------------------------------------------------------------------------------------------------------------------------------------- 
Sort (cost=36515.17..36534.19 rows=7608 width=663) 
    Sort Key: foos.id 
    -> Nested Loop (cost=0.00..33788.21 rows=7608 width=663) 
     -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
       Index Cond: (baz_id = 13266) 
     -> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663) 
       Index Cond: (foos.bar_id = bars.id) 
       Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text)) 
(8 rows) 
+2

+1 para la pregunta interesante. –

+0

Sus consultas no coinciden con sus planes de consulta. Si necesita ayuda, al menos proporcione los detalles relevantes completos ... –

+0

Disculpe las diferentes consultas/planes; Intentaba ofuscar un poco, pero en retrospectiva, no sé por qué. Lo actualizaré mañana con las consultas y planes reales. – jakeboxer

Respuesta

1

Probablemente sucede porque antes de que intenta ordenar a continuación para seleccionar. ¿Por qué no intentas ordenar el resultado en un exterior? Seleccionar todo? Algo así como: SELECT * FROM (SELECT ... INNER JOIN ETC ...) ORDER BY ... DESC

2

Su plan de consulta indica un filtro en

(((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text)) 

que no aparece en el SELECCIONAR: ¿de dónde viene?

Además, tenga en cuenta que la expresión aparece como un "Filtro" y no como un "Índice de Cond", lo que parece indicar que no hay ningún índice aplicado.

+0

Disculpa por esto. No sé por qué estaba tratando de ofuscar. Lo arreglaré por la mañana. – jakeboxer

13

Cuando tiene tanto el LÍMITE como el PEDIDO POR, el optimizador ha decidido que es más rápido cojear a través de los registros no filtrados en foo mediante la tecla descendente hasta que obtiene cinco coincidencias para el resto de los criterios. En los demás casos, simplemente ejecuta la consulta como un bucle anidado y devuelve todos los registros.

Offhand, yo diría que el problema es que PG no asimila la distribución conjunta distribución de los diversos identificadores y es por eso que el plan es tan poco óptimo.

Para posibles soluciones: supongo que ha ejecutado ANALYZE recientemente. Si no, hazlo. Eso puede explicar por qué sus tiempos estimados son altos incluso en la versión que regresa rápidamente. Si el problema persiste, quizás ejecute ORDER BY como una subselección y toque el LIMIT en una consulta externa.

+2

¡Gran comentario, esta fue mi solución! – Geesu

+0

ok ... así 'foos.bars.last' da como resultado un escaneo de índice completo en barras ... nice -_- – Jim

+1

ok ... así que esto da como resultado un escaneo de índice completo solo si foos tiene 0 barras ... Aún molesto aunque – Jim

0

puede estar ejecutando un escaneo de tabla completa en "foos". ¿Intentó cambiar el orden de las tablas y en su lugar utilizar un left-join en lugar de inner-join y ver si muestra los resultados más rápido.

decir ...

SELECT "bars"."id", "foos".* 
FROM "bars" 
LEFT JOIN "foos" ON "bars"."id" = "foos"."bar_id" 
WHERE "bars"."baz_id" = 13266 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0; 
Cuestiones relacionadas