2010-02-25 8 views
19

Pasé más de una hora preguntándome sobre un plan de consulta que no podía entender. La consulta era UDPATE y simplemente no se ejecutaría TOTALMENTE. Totalmente estancado: pg_locks mostró que tampoco esperaba nada. Ahora, no me considero el mejor o el peor tipo de lector de plan de consulta, pero me parece excepcionalmente difícil. Me pregunto ¿cómo se lee esto? ¿Existe alguna metodología que los Pg aces sigan para identificar el error?¿Cómo puedo "pensar mejor" cuando leo un plan de consulta PostgreSQL? (Ejemplo adjunto)

Planeo hacerme otra pregunta sobre cómo solucionar este problema, pero en este momento estoy hablando específicamente en cómo leer este tipo de planes. Por favor, no apunte a ningún tutorial genérico a menos que aborde específicamente este problema, resaltado debajo del plan de consulta.

          QUERY PLAN           
-------------------------------------------------------------------------------------------- 
Nested Loop Anti Join (cost=47680.88..169413.12 rows=1 width=77) 
    Join Filter: ((co.fkey_style = v.chrome_styleid) AND (co.name = o.name)) 
    -> Nested Loop (cost=5301.58..31738.10 rows=1 width=81) 
     -> Hash Join (cost=5301.58..29722.32 rows=229 width=40) 
       Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text)) 
       -> Seq Scan on options io (cost=0.00..20223.32 rows=23004 width=36) 
        Filter: (name IS NULL) 
       -> Hash (cost=4547.33..4547.33 rows=36150 width=24) 
        -> Seq Scan on vehicles iv (cost=0.00..4547.33 rows=36150 width=24) 
          Filter: (date_sold IS NULL) 
     -> Index Scan using options_pkey on options co (cost=0.00..8.79 rows=1 width=49) 
       Index Cond: ((co.fkey_style = iv.chrome_styleid) AND (co.code = io.code)) 
    -> Hash Join (cost=42379.30..137424.09 rows=16729 width=26) 
     Hash Cond: ((v.lot_id = o.lot_id) AND ((v.vin)::text = (o.vin)::text)) 
     -> Seq Scan on vehicles v (cost=0.00..4547.33 rows=65233 width=24) 
     -> Hash (cost=20223.32..20223.32 rows=931332 width=44) 
       -> Seq Scan on options o (cost=0.00..20223.32 rows=931332 width=44) 
(17 rows) 

El problema con este plan de consulta - Creo que entiendo - es probablemente mejor dicho por RhodiumToad (que es definitivamente mejor en esto, así que voy a apostar por su explicación ser mejor) de irc://irc.freenode.net/#postgresql:

oh, ese plan es potencialmente desastroso el problema con ese plan es que se está ejecutando un hashjoin muy costoso para cada fila el problema es la filas = 1 estimación de la otra se unen y el planificador piensa que está bien pon una gran una consulta costosa en la ruta interna de un nestloop donde se estima que el camino externo devuelve solo una fila. dado que, obviamente, según la estimación del planificador, la parte costosa solo se ejecutará una vez pero esto tiene una tendencia obvia a realmente arruinar en la práctica el problema es que el planificador cree que su propia estimación idealmente, el planificador necesita conocer la diferencia entre "estima para volver 1 fila" y "no es posible devolver más de 1 fila" pero no es del todo claro cómo incorporar eso en el código existente

Él va a decir:

puede afectar cualquier unión, pero generalmente se une subconsultas son los más propensos

Ahora cuando leo este plan la primera cosa que noté fue el Nested Loop Anti Join, esto tuvo un costo de 169,413 (me quedo con límites superiores). Este Anti-Join se descompone en el resultado de Nested Loop al costo de 31,738, y el resultado de Hash Join a un costo de 137,424. Ahora, el 137,424, es mucho mayor que 31,738, así que sabía que el problema era el Hash Join. Luego procedo al EXPLAIN ANALYZE el segmento Hash Join fuera de la consulta. Se ejecutó en 7 segundos. Me aseguré de que hubiera índices en (lot_id, vin), y (co.code y v.code) - había. Inhabilité seq_scan y hashjoin individualmente y noté un aumento de velocidad de menos de 2 segundos. No lo suficientemente cerca como para dar cuenta de por qué no estaba progresando después de una hora.

¡Pero, después de todo esto, estoy totalmente equivocado! Sí, era la parte más lenta de la consulta, pero debido al bit rows="1" (supongo que estaba en el Nested Loop Anti Join). ¿Hay algún tutorial que me ayude a identificar este tipo de problemas? Aquí hay un error (falta de habilidad) en el planificador al estimar erróneamente la cantidad de filas.¿Cómo se supone que debo leer esto para llegar a la misma conclusión que RhodiumToad?

¿Es simplemente rows="1" que se supone que me desencadenó a resolver esto?

Ejecuté VACUUM FULL ANALYZE en todas las tablas involucradas, y esto es Postgresql 8.4.

Respuesta

24

Al ver a través de cuestiones como esta requiere un poco de experiencia en donde las cosas pueden ir mal. Pero para encontrar problemas en los planes de consulta, intente validar el plan producido desde adentro hacia afuera, compruebe si el número de filas estimadas es correcto y las estimaciones de costos coinciden con el tiempo empleado. Por cierto. las dos estimaciones de costo no son límites inferiores y superiores, primero es el costo estimado para producir la primera fila de producción, el segundo número es el costo total estimado, consulte explain documentation para obtener más información, también hay algunos planner documentation disponibles. También ayuda a saber cómo funcionan los diferentes métodos de acceso. Como punto de partida Wikipedia tiene información en nested loop, hash y merge joins.

En su ejemplo, usted comenzaría con:

  -> Seq Scan on options io (cost=0.00..20223.32 rows=23004 width=36) 
       Filter: (name IS NULL) 

Run EXPLAIN ANALYZE SELECT * FROM options WHERE name IS NULL; y ver si las filas devueltas coincide con la estimación. Un factor de 2 por lo general no es un problema, estás tratando de detectar las diferencias de orden de magnitud.

Luego, vea EXPLAIN ANALYZE SELECT * FROM vehicles WHERE date_sold IS NULL; devuelve la cantidad esperada de filas.

luego subir un nivel a la unión de comprobación aleatoria:

 -> Hash Join (cost=5301.58..29722.32 rows=229 width=40) 
      Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text)) 

Ver si EXPLAIN ANALYZE SELECT * FROM vehicles AS iv INNER JOIN options io ON (io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text) WHERE iv.date_sold IS NULL AND io.name IS NULL; resultados en 229 filas.

Sube un nivel más agrega INNER JOIN options co ON (co.fkey_style = iv.chrome_styleid) AND (co.code = io.code) y se espera que devuelva solo una fila. Probablemente este sea el problema porque si el número real de filas va de 1 a 100, la estimación del costo total de atravesar el bucle interno del bucle anidado que contiene está desactivado por un factor de 100.

El error subyacente que el planificador está haciendo es probable que espere que los dos predicados para unirse en co sean independientes entre sí y multiplique sus selectividades. Aunque en realidad pueden estar fuertemente correlacionados y la selectividad está más cerca de MIN (s1, s2) que s1 * s2.

+0

Esta es una gran respuesta, pero ¿de qué estás hablando cuando dices 'join in co'? Creo que la explicación de 'RhodiumToads' del problema porque suena exacto? ¿Estás explicando lo mismo o algo diferente? –

+0

Lo mismo. Unirse en 'co' es el' Index Scan usando options_pkey en las opciones co' del nodo interno de la unión de bucle anidado. Tiene dos condiciones que, probablemente, el planificador, de forma poco realista, dará como resultado una fila de producción.Si intenta ejecutar esa consulta y ver cuántas filas realmente devuelve, puede verificar si este es el caso. Las estimaciones incorrectas para los predicados correlacionados son un problema conocido. Hay un debate sobre esto en la lista de rendimiento: http://archives.postgresql.org/pgsql-performance/2009-06/msg00055.php –

2

¿ANALIZASte las tablas? ¿Y qué dice pg_stats sobre estas tablas? El plan de consulta se basa en las estadísticas, estas tienen que estar bien. ¿Y qué versión usas? 8.4?

Los costos se pueden calcular utilizando las estadísticas, la cantidad de relings, la cantidad de filas y la configuración en postgresql.conf para las Constantes de costos del planificador.

work_mem también está involucrado, que podría ser demasiado baja y forzar el planificador para hacer un SeqScan, para matar el rendimiento ...

+0

He ejecutado 'VACUUM FULL ANALYZE' en todas las tablas involucradas, y este es Postgresql 8.4. –

Cuestiones relacionadas