У меня есть представление vote_pairs
, которое выглядит так:
CREATE VIEW vote_pairs AS
SELECT
v1.name as name1,
v2.name as name2,
...
FROM votes AS v1
JOIN votes AS v2
ON v1.topic_id = v2.topic_id;
А с ~100 тыс. строк в таблице votes
выполнение запросов в этом представлении занимает около 3 секунд.
Однако, когда я добавляю дополнительный фильтр по именам:
… ON v1.topic_id = v2.topic_id AND v1.name < v2.name;
Время выполнения увеличилось в четыре раза, и на выполнение запросов через vote_pairs
уходит почти 12 секунд.
Эта среда выполнения непротиворечива независимо от положения ограничения… Например, запрос одинаково медленный, если фильтр перемещен в предложение WHERE
внешнего запроса:
SELECT * FROM vote_pairs WHERE name1 < name2;
В чем дело? Медленны ли лексикографические сравнения в Postgres? Это что-то другое? И как я могу улучшить скорость этого запроса?
Таблица голосования:
CREATE TABLE votes (
topic_id INTEGER REFERENCES topics(id),
name VARCHAR(64),
vote VARCHAR(12)
)
CREATE INDEX votes_topic_name ON votes (topic_id, name);
CREATE INDEX votes_name ON votes (name);
Вывод EXPLAIN ANALYZE
без фильтра имен:
db=# CREATE OR REPLACE VIEW vote_pairs AS
db-# SELECT
db-# v1.name as name1,
db-# v2.name as name2
db-# FROM votes AS v1
db-# JOIN votes AS v2
db-# ON v1.topic_id = v2.topic_id;
CREATE VIEW
db=# EXPLAIN ANALYZE SELECT * FROM vote_pairs; QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=3956.38..71868.56 rows=5147800 width=28) (actual time=51.810..1236.673 rows=5082750 loops=1)
Hash Cond: (v1.topic_id = v2.topic_id)
-> Seq Scan on votes v1 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.019..18.358 rows=112950 loops=1)
-> Hash (cost=1882.50..1882.50 rows=112950 width=18) (actual time=50.671..50.671 rows=112950 loops=1)
-> Seq Scan on votes v2 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.004..20.306 rows=112950 loops=1)
Total runtime: 1495.963 ms
(6 rows)
И с фильтром:
db=# CREATE OR REPLACE VIEW vote_pairs AS
db-# SELECT
db-# v1.name as name1,
db-# v2.name as name2
db-# FROM votes AS v1
db-# JOIN votes AS v2
db-# ON v1.topic_id = v2.topic_id AND v1.name < v2.name;
CREATE VIEW
db=# EXPLAIN ANALYZE SELECT * FROM vote_pairs;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=3956.38..84738.06 rows=1715933 width=28) (actual time=66.688..6900.478 rows=2484900 loops=1)
Hash Cond: (v1.topic_id = v2.topic_id)
Join Filter: ((v1.name)::text < (v2.name)::text)
-> Seq Scan on votes v1 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.023..24.539 rows=112950 loops=1)
-> Hash (cost=1882.50..1882.50 rows=112950 width=18) (actual time=65.603..65.603 rows=112950 loops=1)
-> Seq Scan on votes v2 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.004..26.756 rows=112950 loops=1)
Total runtime: 7048.740 ms
(7 rows)
ОБЪЯСНИТЬ (АНАЛИЗИРОВАТЬ, БУФЕРЫ):
db=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM vote_pairs;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=3956.38..71345.89 rows=5152008 width=28) (actual time=56.230..1204.522 rows=5082750 loops=1)
Hash Cond: (v1.topic_id = v2.topic_id)
Buffers: shared hit=129 read=1377 written=2, temp read=988 written=974
-> Seq Scan on votes v1 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.008..20.492 rows=112950 loops=1)
Buffers: shared hit=77 read=676
-> Hash (cost=1882.50..1882.50 rows=112950 width=18) (actual time=55.742..55.742 rows=112950 loops=1)
Buckets: 2048 Batches: 8 Memory Usage: 752kB
Buffers: shared hit=52 read=701 written=2, temp written=480
-> Seq Scan on votes v2 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.004..22.954 rows=112950 loops=1)
Buffers: shared hit=52 read=701 written=2
Total runtime: 1499.302 ms
(11 rows)
db=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM vote_pairs WHERE name1 > name2;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=3956.38..84225.91 rows=1717336 width=28) (actual time=51.214..6422.592 rows=2484900 loops=1)
Hash Cond: (v1.topic_id = v2.topic_id)
Join Filter: ((v1.name)::text > (v2.name)::text)
Rows Removed by Join Filter: 2597850
Buffers: shared hit=32 read=1477, temp read=988 written=974
-> Seq Scan on votes v1 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.008..22.605 rows=112950 loops=1)
Buffers: shared hit=27 read=726
-> Hash (cost=1882.50..1882.50 rows=112950 width=18) (actual time=50.678..50.678 rows=112950 loops=1)
Buckets: 2048 Batches: 8 Memory Usage: 752kB
Buffers: shared hit=2 read=751, temp written=480
-> Seq Scan on votes v2 (cost=0.00..1882.50 rows=112950 width=18) (actual time=0.005..21.337 rows=112950 loops=1)
Buffers: shared hit=2 read=751
Total runtime: 6573.308 ms
(13 rows)
Разные примечания:
VACCUM FULL
иANALYZE votes
запущены- И 8.4.11, и 9.2.3 ведут себя одинаково
EXPLAIN ANALYZE <query>
? - person Alex Gaynor   schedule 09.06.2013work_mem
? Я знаю, что на самом деле это не решение, но если это поможет, это может быть ключом к тому, что не так. - person Tom Anderson   schedule 10.06.2013votes
? Ну да — я мог бы определить ключPRIMARY KEY (topic_id, name)
, но в данном случае это ничего бы не изменило. И да — результат представляет собой декартово произведение имен и голосов, которое я использую в качестве промежуточного шага при расчете отношения между избирателями (например, часто ли люди А и Б голосуют вместе?). Так что в этом случае да — мой реальная проблема в том, что фильтрация очень медленная. - person David Wolever   schedule 10.06.2013VACUUM ANALYZE votes
безFULL
? И, пожалуйста, включите выводEXPLAIN (ANALYZE, BUFFERS) …
, он будет работать только в версиях 9.*. - person vyegorov   schedule 10.06.2013working_mem = 100MB
, кажется, ничего не меняется. - person David Wolever   schedule 10.06.2013EXPLAIN (ANALYZE, BUFFERS)
после запускаVACUUM ANALYZE votes
. - person David Wolever   schedule 10.06.2013name
кажется скрытым доменом (должен быть (целочисленный) FK для таблицы лиц, а голосование может быть целым, или даже логическим, или перечислимым) - person wildplasser   schedule 10.06.2013CREATE TABLE persons (id SERIAL PRIMARY KEY, name VARCHAR(64))
… Но это усложнило бы мой вариант использования (по разным веским причинам). - person David Wolever   schedule 10.06.2013VIEW
, которые помогут вам. - person wildplasser   schedule 10.06.2013