Estou executando o PostgresSQL 9.2 e tenho uma relação de 12 colunas com cerca de 6.700.000 linhas. Ele contém nós em um espaço 3D, cada um referenciando um usuário (quem o criou). Para consultar qual usuário criou quantos nós eu faço o seguinte (adicionado explain analyze
para obter mais informações):
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 você pode ver, isso leva cerca de 1,7 segundos. Isso não é tão ruim, considerando a quantidade de dados, mas me pergunto se isso pode ser melhorado. Tentei adicionar um índice BTree na coluna do usuário, mas isso não ajudou em nada.
Você tem sugestões alternativas?
Por uma questão de completude, esta é a definição de tabela completa com todos os seus índices (sem restrições de chave estrangeira, referências e gatilhos):
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)
Edit: Este é o resultado, quando eu uso a consulta (e índice) proposta por @ypercube (a consulta leva cerca de 5,3 segundos sem 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
Edit 2: Este é o resultado, quando eu uso um index
on project_id, user_id
(mas ainda não otimizamos o esquema) como @ erwin-brandstetter sugerido (a consulta é executada com 1,5 segundos na mesma velocidade da minha 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)
fonte
Users
comuser_id
a chave primária?project_id
euser_id
? A tabela é atualizada continuamente ou você pode trabalhar com uma visão materializada (por algum tempo)?Respostas:
O principal problema é o índice ausente. Mas tem mais.
Você tem muitas
bigint
colunas. Provavelmente um exagero. Normalmente,integer
é mais que suficiente para colunas comoproject_id
euser_id
. Isso também ajudaria o próximo item.Ao otimizar a definição da tabela, considere esta resposta relacionada, com ênfase no alinhamento e preenchimento dos dados . Mas a maior parte do resto também se aplica:
O elefante na sala : não há índice
project_id
. Crie um. Isso é mais importante que o restante desta resposta.Enquanto estiver nisso, crie um índice com várias colunas:
Se você seguisse meu conselho,
integer
seria perfeito aqui:user_id
é definidoNOT NULL
,count(user_id)
é equivalente acount(*)
, mas o último é um pouco mais rápido e mais rápido. (Nesta consulta específica, isso se aplica mesmo semuser_id
ser definidoNOT NULL
.)id
já é a chave primária, aUNIQUE
restrição adicional é um lastro inútil . Largue:Além: eu não usaria
id
como nome da coluna. Use algo descritivo comotreenode_id
.Informação adicionada
Q:
How many different project_id and user_id?
A:
not more than five different project_id
.Isso significa que o Postgres precisa ler cerca de 20% da tabela inteira para satisfazer sua consulta. A menos que ele possa usar uma varredura apenas de índice , uma varredura seqüencial na tabela será mais rápida do que envolve qualquer índice. Não há mais desempenho a ganhar aqui - exceto otimizando as configurações da tabela e do servidor.
Quanto à varredura apenas de índice : Para ver quão eficaz isso pode ser, execute
VACUUM ANALYZE
se você puder pagar por isso (bloqueia a tabela exclusivamente). Em seguida, tente sua consulta novamente. Agora deve ser moderadamente mais rápido usando apenas o índice. Leia esta resposta relacionada primeiro:Assim como a página de manual adicionada ao Postgres 9.6 e ao Wiki do Postgres em verificações apenas de índice .
fonte
user_id
eproject_id
integer
deve ser mais do que suficiente. Usar emcount(*)
vez decount(user_id)
economizar cerca de 70ms aqui, é bom saber. AdicioneiEXPLAIN ANALYZE
a consulta depois de adicionar sua sugestãoindex
à primeira postagem. Porém, não melhora o desempenho (mas também não dói). Parece queindex
não é usado. Testarei as otimizações do esquema em breve.seqscan
, o índice será usado (Index Only Scan using treenode_project_id_user_id_index on treenode
), mas a consulta levará cerca de 2,5 segundos (o que é cerca de 1 segundo a mais que com seqscan).Primeiro eu adicionaria um índice
(project_id, user_id)
e, na versão 9.3, tente esta consulta:Na 9.2, tente este:
Presumo que você tenha uma
users
mesa. Caso contrário, substituausers
por:(SELECT DISTINCT user_id FROM treenode)
fonte
CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);
mas tentei também sem aUSING
cláusula. Perco alguma coisa?users
tabela e quantas linhas a consulta retorna (quantos usuários existemproject_id=1
)? Você pode mostrar a explicação dessa consulta depois de adicionar o índice?index
local. Desculpe pela confusão. Na minhausers
mesa, tenho 46 entradas. A consulta retorna apenas 9 linhas. Surpreendentemente,SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
retorna 38 linhas. Adicionei oexplain
ao meu primeiro post. E para evitar confusão: minhausers
mesa é chamada de verdadeauth_user
.SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
retornar 38 linhas, enquanto as consultas retornam apenas 9. Buffled.SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;