Índice que no se usa, pero que influye en la consulta

8

Tengo una tabla PostgreSQL 9.3 con algunos números y algunos datos adicionales:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Actualmente, esta tabla tiene aproximadamente 10 millones de registros y ocupa 1 GB de espacio en disco. myidNo son consecutivos.

Quiero calcular cuántas filas hay en cada bloque de 100000 números consecutivos:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Esto devuelve alrededor de 3500 filas.

Noté que la existencia de un cierto índice acelera significativamente esta consulta a pesar de que el plan de consulta no lo menciona en absoluto. El plan de consulta sin el índice:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

El índice:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

El nuevo plan de consulta:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Entonces, los planes de consulta y los tiempos de ejecución difieren significativamente (casi tres veces) pero ninguno menciona el índice. Este comportamiento es perfectamente reproducible en mi máquina de desarrollo: pasé por varios ciclos de caída del índice, probando la consulta varias veces, recreando el índice, nuevamente probando la consulta varias veces. ¿Que esta pasando aqui?

liori
fuente
No soy experto en analizar los planes de consulta de Postgres, pero supongo que el índice se usa para el HashAggregatemétodo (y no se requiere clasificación), por lo que obtienes un mejor rendimiento. Por qué el índice no se menciona en el plan, no tengo ni idea.
ypercubeᵀᴹ
¿La salida del plan cambia si habilita el modo detallado usando explain (analyze true, verbose true) ...:?
a_horse_with_no_name
Sería genial si pudieras reducirlo a un caso de prueba autónomo. Seguro que parece extraño.
Craig Ringer
@a_horse_with_no_name: Sí, cambia: he reemplazado los planes de consulta por los detallados de la pregunta. Pero ese plan de consulta aún no menciona el índice en absoluto.
liori
Si hay más estadísticas disponibles (especialmente cardinalidad y posiblemente valores mínimos / máximos) en la columna de identificación con el índice que sin él, eso podría cambiar el grupo del optimizador por selección de método, incluso si no termina usando el índice en absoluto . (No conozco el optimizador y las estadísticas de postgres en absoluto, así que no tengo idea de si ese podría ser el caso o no.)
Mat

Respuestas:

3

VACUUM ANALYZEhace la diferencia en tu ejemplo. Además, como proporcionó @jjanes , las estadísticas adicionales para el índice funcional. Por documentación:

pg_statisticTambién almacena datos estadísticos sobre los valores de las expresiones de índice. Estos se describen como si fueran columnas de datos reales; en particular, hace starelidreferencia al índice. Sin embargo, no se realiza ninguna entrada para una columna de índice de no expresión ordinaria, ya que sería redundante con la entrada para la columna de la tabla subyacente.

Sin embargo, crear el índice por sí solo no hace que Postgres recopile estadísticas. Tratar:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

No devuelve nada hasta que ejecuta su primer ANALYZE(o VACUUM ANALYZE, o el demonio autovacuum entra en acción).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Ahora verás estadísticas adicionales.

Dado que la tabla completa tiene que leerse de todos modos, Postgres usará un escaneo secuencial a menos que espere que el cálculo myid/100000sea ​​lo suficientemente costoso como para cambiar, lo cual no es así.

Su única otra posibilidad sería una exploración de solo índice si el índice es mucho más pequeño que la tabla, y se cumplen las condiciones previas para una exploración de solo índice. Detalles en el Wiki de Postgres y en el manual .

Mientras no se use ese índice funcional, el beneficio colateral de las estadísticas agregadas es moderado. Si la tabla fuera de solo lectura, el costo sería bajo, pero nuevamente, probablemente veríamos un escaneo de solo índice de inmediato.

Tal vez también pueda lograr mejores planes de consulta estableciendo un objetivo de estadísticas más alto para mytable.myid. Eso solo incurriría en un costo menor. Más:

Erwin Brandstetter
fuente
Gracias por esta explicación, es muy útil para comprender el problema. En mi caso, lo más probable es que necesite una myid/100000 BETWEEN somevalue AND othervaluecondición adicional , de modo que el índice se utilizará en el plan de consulta de todos modos: acabo de hacer esta pregunta porque no entendí por qué el índice es útil en el caso de la tabla completa.
liori
@liori: podría cubrir eso con WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(considere los efectos de redondeo según sus tipos), y probablemente ya tenga un índice simple myid, por lo que puede prescindir de un índice especializado adicional. Podría ser más eficiente.
Erwin Brandstetter
6

Cuando crea un índice de expresión, hace que PostgreSQL recopile estadísticas sobre esa expresión. Con esas estadísticas en la mano, ahora tiene una estimación precisa del número de filas agregadas que devolverá la consulta, lo que la lleva a hacer una mejor elección de plan.

Específicamente en este caso, sin esas estadísticas adicionales, pensó que la tabla hash sería demasiado grande para caber en work_mem, por lo que no eligió ese método.

jjanes
fuente
Creo que el planificador no tiene work_memen cuenta el valor de . Si lo planteó para que el tipo se ajuste a la memoria, aún usaría el mismo plan. Permítanme señalar aquí que la diferencia horaria (la mayor parte) proviene del tipo de disco externo.
dezso
1
@dezso ¿Qué sucede si experimentalmente duplica o triplica el valor de work_mem que se necesitaba para encajar en la memoria? La clasificación y el hash tienen estimaciones generales diferentes, y las estimaciones en sí mismas no son muy precisas. Además, ¿qué versión menor de 9.3 está utilizando?
jjanes