Я использую PostgresSQL 9.2 и имею отношение в 12 столбцов с 6 700 000 строк. Он содержит узлы в трехмерном пространстве, каждый из которых ссылается на пользователя (который его создал). Чтобы запросить, какой пользователь создал, сколько узлов я делаю следующее (добавлено explain analyze
для получения дополнительной информации):
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
Как видите, это занимает около 1,7 секунды. Это не так уж плохо, учитывая объем данных, но мне интересно, можно ли это улучшить. Я пытался добавить индекс BTree в пользовательский столбец, но это никак не помогло.
У вас есть альтернативные предложения?
Ради полноты, это полное определение таблицы со всеми ее индексами (без ограничений внешнего ключа, ссылок и триггеров):
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)
Изменить: Это результат, когда я использую запрос (и индекс), предложенный @ypercube (запрос занимает около 5,3 секунды без 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
Редактировать 2: Это результат, когда я использую index
on project_id, user_id
(но пока не оптимизирую схему), как предложил @ erwin-brandstetter (запрос выполняется с 1,5 секундами с той же скоростью, что и мой исходный запрос):
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
сuser_id
первичным ключом?project_id
аuser_id
? Таблица обновляется постоянно или вы можете работать с материализованным представлением (какое-то время)?Ответы:
Основная проблема - отсутствующий индекс. Но это еще не все.
У вас много
bigint
столбцов. Вероятно, перебор. Как правило, этогоinteger
более чем достаточно для столбцов типаproject_id
иuser_id
. Это также поможет следующий пункт.При оптимизации определения таблицы рассмотрите этот связанный ответ с акцентом на выравнивание и заполнение данных . Но большинство остального тоже применимо:
Слон в комнате : нет индекса
project_id
. Создай. Это важнее, чем остальная часть этого ответа.Находясь в этом, сделайте это многоколоночным индексом:
Если бы вы следовали моему совету,
integer
было бы идеально здесь:user_id
определяетсяNOT NULL
, такcount(user_id)
что эквивалентноcount(*)
, но последний немного короче и быстрее. (В этом конкретном запросе это применимо даже безuser_id
определенияNOT NULL
.)id
это уже первичный ключ, дополнительнымUNIQUE
ограничением является бесполезный балласт . Брось это:В сторону: я бы не использовал в
id
качестве имени столбца. Используйте что-то вроде описанияtreenode_id
.Добавлена информация
В:
How many different project_id and user_id?
А:
not more than five different project_id
.Это означает, что Postgres должен прочитать около 20% всей таблицы, чтобы удовлетворить ваш запрос. Если он не может использовать сканирование только по индексу , последовательное сканирование таблицы будет быстрее, чем использование каких-либо индексов. Здесь больше не нужно повышать производительность, кроме как за счет оптимизации настроек таблицы и сервера.
Что касается сканирования только по индексу : чтобы увидеть, насколько эффективным это может быть, запустите,
VACUUM ANALYZE
если вы можете себе это позволить (блокирует исключительно таблицу). Затем попробуйте еще раз. Теперь он должен быть немного быстрее, используя только индекс. Сначала прочтите этот связанный ответ:А также страница справочника, добавленная с Postgres 9.6 и Postgres Wiki по сканированию только по индексу .
источник
user_id
иproject_id
integer
должно быть более чем достаточно. Использованиеcount(*)
вместоcount(user_id)
сохранения около 70 мс, это хорошо знать. Я добавилEXPLAIN ANALYZE
запрос после того, как добавил предложенноеindex
в первый пост. Это не улучшает производительность, хотя (но и не повредит). Кажется,index
не используется вообще. Я скоро опробую оптимизацию схемы.seqscan
, используется индекс (Index Only Scan using treenode_project_id_user_id_index on treenode
), но тогда запрос занимает около 2,5 секунд (что примерно на 1 секунду дольше, чем с помощью seqscan).Сначала я добавлю индекс,
(project_id, user_id)
а затем в версии 9.3 попробую этот запрос:В 9.2 попробуйте это:
Я полагаю, у вас есть
users
стол. Если нет, заменитеusers
на:(SELECT DISTINCT user_id FROM treenode)
источник
CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);
но я попытался и безUSING
предложения. Я что-то пропустил?users
таблице и сколько строк возвращает запрос (сколько у них пользователейproject_id=1
)? Можете ли вы показать объяснение этого запроса после добавления индекса?index
месте. Извините за путаницу. В моейusers
таблице 46 записей. Запрос возвращает только 9 строк. Удивительно, ноSELECT DISTINCT user_id FROM treenode WHERE project_id=1;
возвращает 38 строк. Я добавилexplain
свой первый пост. И чтобы избежать путаницы: мойusers
стол на самом деле называетсяauth_user
.SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
вернуть 38 строк, тогда как запросы возвращают только 9. Buffled.SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;