Estoy ejecutando PostgresSQL 9.2 y tengo una relación de 12 columnas con aproximadamente 6,700,000 filas. Contiene nodos en un espacio 3D, cada uno de los cuales hace referencia a un usuario (que lo creó). Para consultar qué usuario ha creado cuántos nodos hago lo siguiente (agregado explain analyze
para obtener más información):
EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
-> Seq Scan on treenode (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
Filter: (project_id = 1)
Total runtime: 1747.653 ms
Como puede ver, esto toma alrededor de 1.7 segundos. Esto no es tan malo teniendo en cuenta la cantidad de datos, pero me pregunto si esto puede mejorarse. Traté de agregar un índice BTree en la columna de usuario, pero esto no ayudó de ninguna manera.
¿Tienes sugerencias alternativas?
En aras de la exhaustividad, esta es la definición de tabla completa con todos sus índices (sin restricciones de clave externa, referencias y desencadenantes):
Column | Type | Modifiers
---------------+--------------------------+------------------------------------------------------
id | bigint | not null default nextval('concept_id_seq'::regclass)
user_id | bigint | not null
creation_time | timestamp with time zone | not null default now()
edition_time | timestamp with time zone | not null default now()
project_id | bigint | not null
location | double3d | not null
reviewer_id | integer | not null default (-1)
review_time | timestamp with time zone |
editor_id | integer |
parent_id | bigint |
radius | double precision | not null default 0
confidence | integer | not null default 5
skeleton_id | bigint |
Indexes:
"treenode_pkey" PRIMARY KEY, btree (id)
"treenode_id_key" UNIQUE CONSTRAINT, btree (id)
"skeleton_id_treenode_index" btree (skeleton_id)
"treenode_editor_index" btree (editor_id)
"treenode_location_x_index" btree (((location).x))
"treenode_location_y_index" btree (((location).y))
"treenode_location_z_index" btree (((location).z))
"treenode_parent_id" btree (parent_id)
"treenode_user_index" btree (user_id)
Editar: este es el resultado, cuando uso la consulta (e índice) propuesta por @ypercube (la consulta tarda aproximadamente 5,3 segundos sin EXPLAIN ANALYZE
):
EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on auth_user u (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
SubPlan 1
-> Aggregate (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
-> Bitmap Heap Scan on treenode t (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
Recheck Cond: ((project_id = 1) AND (user_id = u.id))
Rows Removed by Index Recheck: 461076
-> Bitmap Index Scan on treenode_user_index (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
Index Cond: ((project_id = 1) AND (user_id = u.id))
Total runtime: 5556.190 ms
(9 rows)
Time: 5556.804 ms
Edición 2: este es el resultado, cuando uso un index
encendido project_id, user_id
(pero todavía no hay optimización de esquema) como sugirió @ erwin-brandstetter (la consulta se ejecuta con 1,5 segundos a la misma velocidad que mi consulta original):
EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
-> Seq Scan on treenode (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
Filter: (project_id = 1)
Total runtime: 1807.368 ms
(4 rows)
Users
conuser_id
como la clave principal?project_id
yuser_id
? ¿La tabla se actualiza continuamente o podría trabajar con una vista materializada (por algún tiempo)?Respuestas:
El problema principal es el índice que falta. Pero hay más.
Tienes muchas
bigint
columnas Probablemente exagerado. Por lo general,integer
es más que suficiente para columnas comoproject_id
yuser_id
. Esto también ayudaría al siguiente elemento.Mientras optimiza la definición de la tabla, considere esta respuesta relacionada, con énfasis en la alineación de datos y el relleno . Pero la mayor parte del resto también se aplica:
El elefante en la habitación : no hay índice
project_id
. Crea uno. Esto es más importante que el resto de esta respuesta.Mientras lo hace, haga que sea un índice de varias columnas:
Si siguieras mi consejo,
integer
sería perfecto aquí:user_id
está definidoNOT NULL
, por lo quecount(user_id)
es equivalente acount(*)
, pero este último es un poco más corto y más rápido. (En esta consulta específica, esto incluso se aplicaría sinuser_id
estar definidoNOT NULL
).id
ya es la clave principal, laUNIQUE
restricción adicional es el lastre inútil . Déjalo caer:Aparte: no lo usaría
id
como nombre de columna. Use algo descriptivo comotreenode_id
.Información agregada
Q:
How many different project_id and user_id?
A:
not more than five different project_id
.Eso significa que Postgres tiene que leer aproximadamente el 20% de toda la tabla para satisfacer su consulta. A menos que pueda usar un escaneo de solo índice , un escaneo secuencial en la tabla será más rápido que cualquier índice. No hay más rendimiento que ganar aquí, excepto mediante la optimización de la tabla y la configuración del servidor.
En cuanto a la exploración de solo índice : para ver qué tan efectivo puede ser, ejecute
VACUUM ANALYZE
si puede permitírselo (bloquea la tabla exclusivamente). Luego intente su consulta nuevamente. Ahora debería ser moderadamente más rápido usando solo el índice. Lea esta respuesta relacionada primero:Además de la página del manual agregada con Postgres 9.6 y Postgres Wiki en escaneos de solo índice .
fuente
user_id
yproject_id
integer
debería ser más que suficiente. Usar encount(*)
lugar decount(user_id)
guardar unos 70 ms aquí, es bueno saberlo. He agregado elEXPLAIN ANALYZE
de la consulta después de haber agregado su sugerenciaindex
a la primera publicación. Sin embargo, no mejora el rendimiento (pero tampoco duele). Pareceindex
que no se usa en absoluto. Probaré las optimizaciones del esquema pronto.seqscan
, se usa el índice (Index Only Scan using treenode_project_id_user_id_index on treenode
), pero la consulta tarda aproximadamente 2.5 segundos (que es aproximadamente 1 segundo más que con seqscan).Primero agregaría un índice
(project_id, user_id)
y luego en la versión 9.3, intente esta consulta:En 9.2, prueba este:
Supongo que tienes una
users
mesa. Si no, reemplaceusers
con:(SELECT DISTINCT user_id FROM treenode)
fuente
CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);
pero intenté también sin laUSING
cláusula. ¿Echo de menos algo?users
tabla y cuántas filas devuelve la consulta (entonces, ¿cuántos usuarios hayproject_id=1
)? ¿Puede mostrar la explicación de esta consulta después de agregar el índice?index
lugar. Perdón por la confusion. En miusers
mesa tengo 46 entradas. La consulta solo devuelve 9 filas. Sorprendentemente,SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
devuelve 38 filas. He añadido elexplain
a mi primera publicación. Y para evitar confusiones: miusers
mesa se llama realmenteauth_user
.SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
devolver 38 filas mientras que las consultas devuelven solo 9. Buffled.SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;