PostgreSQL с использованием значений по умолчанию, плюс
default_statistics_target=1000
random_page_cost=1.5
Версия
PostgreSQL 10.4 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit
Я пылесосил и анализировал. Запрос очень прост:
SELECT r.price
FROM account_payer ap
JOIN account_contract ac ON ap.id = ac.account_payer_id
JOIN account_schedule "as" ON ac.id = "as".account_contract_id
JOIN schedule s ON "as".id = s.account_schedule_id
JOIN rate r ON s.id = r.schedule_id
WHERE ap.account_id = 8
Каждый id
столбец является первичным ключом, и все, к чему присоединяется, является отношением внешнего ключа, и каждый внешний ключ имеет индекс. Плюс индекс для account_payer.account_id
.
Требуется 3,93 с, чтобы вернуть 76 тыс. Строк.
Merge Join (cost=8.06..83114.08 rows=3458267 width=6) (actual time=0.228..3920.472 rows=75548 loops=1)
Merge Cond: (s.account_schedule_id = "as".id)
-> Nested Loop (cost=0.57..280520.54 rows=6602146 width=14) (actual time=0.163..3756.082 rows=448173 loops=1)
-> Index Scan using schedule_account_schedule_id_idx on schedule s (cost=0.14..10.67 rows=441 width=16) (actual time=0.035..0.211 rows=89 loops=1)
-> Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r (cost=0.43..486.03 rows=15005 width=10) (actual time=0.025..39.903 rows=5036 loops=89)
Index Cond: (schedule_id = s.id)
-> Materialize (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)
-> Nested Loop (cost=0.43..49.32 rows=55 width=8) (actual time=0.048..1.110 rows=66 loops=1)
-> Nested Loop (cost=0.29..27.46 rows=105 width=16) (actual time=0.030..0.616 rows=105 loops=1)
-> Index Scan using account_schedule_pkey on account_schedule "as" (cost=0.14..6.22 rows=105 width=16) (actual time=0.014..0.098 rows=105 loops=1)
-> Index Scan using account_contract_pkey on account_contract ac (cost=0.14..0.20 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=105)
Index Cond: (id = "as".account_contract_id)
-> Index Scan using account_payer_pkey on account_payer ap (cost=0.14..0.21 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=105)
Index Cond: (id = ac.account_payer_id)
Filter: (account_id = 8)
Rows Removed by Filter: 0
Planning time: 5.843 ms
Execution time: 3929.317 ms
Если я установлю join_collapse_limit=1
, это займет 0,16 с, 25-кратное ускорение.
Nested Loop (cost=6.32..147323.97 rows=3458267 width=6) (actual time=8.908..151.860 rows=75548 loops=1)
-> Nested Loop (cost=5.89..390.23 rows=231 width=8) (actual time=8.730..11.655 rows=66 loops=1)
Join Filter: ("as".id = s.account_schedule_id)
Rows Removed by Join Filter: 29040
-> Index Scan using schedule_pkey on schedule s (cost=0.27..17.65 rows=441 width=16) (actual time=0.014..0.314 rows=441 loops=1)
-> Materialize (cost=5.62..8.88 rows=55 width=8) (actual time=0.001..0.011 rows=66 loops=441)
-> Hash Join (cost=5.62..8.61 rows=55 width=8) (actual time=0.240..0.309 rows=66 loops=1)
Hash Cond: ("as".account_contract_id = ac.id)
-> Seq Scan on account_schedule "as" (cost=0.00..2.05 rows=105 width=16) (actual time=0.010..0.028 rows=105 loops=1)
-> Hash (cost=5.02..5.02 rows=48 width=8) (actual time=0.178..0.178 rows=61 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
-> Hash Join (cost=1.98..5.02 rows=48 width=8) (actual time=0.082..0.143 rows=61 loops=1)
Hash Cond: (ac.account_payer_id = ap.id)
-> Seq Scan on account_contract ac (cost=0.00..1.91 rows=91 width=16) (actual time=0.007..0.023 rows=91 loops=1)
-> Hash (cost=1.64..1.64 rows=27 width=8) (actual time=0.048..0.048 rows=27 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 10kB
-> Seq Scan on account_payer ap (cost=0.00..1.64 rows=27 width=8) (actual time=0.009..0.023 rows=27 loops=1)
Filter: (account_id = 8)
Rows Removed by Filter: 24
-> Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r (cost=0.43..486.03 rows=15005 width=10) (actual time=0.018..1.685 rows=1145 loops=66)
Index Cond: (schedule_id = s.id)
Planning time: 4.692 ms
Execution time: 160.585 ms
Эти выводы имеют мало смысла для меня. Первый имеет (очень высокую) стоимость 280 500 для соединения с вложенным циклом для графика и индексов скорости. Почему PostgreSQL в первую очередь намеренно выбирает это очень дорогое соединение?
Дополнительная информация запрашивается через комментарии
Является ли
rate_schedule_id_code_modifier_facility_idx
индекс соединения?
Это с schedule_id
первым столбцом. Я сделал его выделенным индексом, и он выбирается планировщиком запросов, но он не влияет на производительность и не влияет на план.
источник
default_statistics_target
иrandom_page_cost
вернуться к их настройкам по умолчанию? Что происходит, когда вы повышаетеdefault_statistics_target
еще больше? Можете ли вы сделать БД Fiddle (на dbfiddle.uk) и попытаться воспроизвести проблему там?work_mem
? Изменение это дает разные сроки?Ответы:
Кажется, либо ваша статистика не точна (запустите анализ вакуума, чтобы обновить их), либо у вас есть коррелированные столбцы в вашей модели (и вам нужно будет выполнить
create statistics
эту операцию, чтобы проинформировать планировщика об этом факте).join_collapse
Параметр позволяет планировщик переставить соединяет поэтому он выполняет сначала один , который извлекает меньше данных. Но для производительности мы не можем позволить планировщику сделать это по запросу с большим количеством объединений. По умолчанию установлено максимум 8 соединений. Установив значение 1, вы просто отключаете эту способность.Так как же postgres предвидит, сколько строк должен получить этот запрос? Он использует статистику для оценки количества строк.
То, что мы можем видеть в ваших планах объяснения, состоит в том, что существует несколько неточных оценок числа строк (первое значение является оценочным, второе - действительным).
Например, здесь:
Планировщик оценил, чтобы получить 55 рядов, когда он фактически получил 74697.
Что бы я сделал (если бы я был на вашем месте):
analyze
пять таблиц для обновления статистикиexplain analyze
CREATE STATISTICS
(документация здесь )Если вам нужна дополнительная информация об оценках строк и их вычислениях, вы найдете все, что вам нужно, в беседе Томаса Вондры «Создайте статистику - для чего она?» (слайды здесь )
источник