У меня в базе данных две таблицы (address и person_address). Адрес имеет PK в address_id. person_address имеет PK на (address_id, person_id, usage_code)
При объединении этих двух таблиц через address_id я ожидаю, что индекс PK будет использоваться в обоих случаях. Однако Postgres добавляет в план шаги сортировки и материализации, что замедляет выполнение запроса. Я попытался сбросить индексы (у person_address был индекс на address_id), проанализировать статистику, но безуспешно.
Я буду признателен за любую помощь в том, как изолировать эту ситуацию, поскольку эти запросы выполняются медленнее, чем ожидалось, в нашей производственной среде.
Это запрос:
select *
from person_addresses pa
join address a
on pa.address_id = a.address_id
Это план:
Merge Join (cost=1506935.96..2416648.39 rows=16033774 width=338)
Merge Cond: (pa.address_id = ((a.address_id)::numeric))
-> Index Scan using person_addresses_pkey on person_addresses pa (cost=0.43..592822.76 rows=5256374 width=104)
-> Materialize (cost=1506935.53..1526969.90 rows=4006874 width=234)
-> Sort (cost=1506935.53..1516952.71 rows=4006874 width=234)
Sort Key: ((a.address_id)::numeric)
-> Seq Scan on address a (cost=0.00..163604.74 rows=4006874 width=234)
Спасибо.
Редактировать 1. После комментария проверил типы данных и обнаружил несоответствие. Исправление типа данных изменило план на следующий
Hash Join (cost=343467.18..881125.47 rows=5256374 width=348)
Hash Cond: (pa.address_id = a.address_id)
-> Seq Scan on person_addresses pa (cost=0.00..147477.74 rows=5256374 width=104)
-> Hash (cost=159113.97..159113.97 rows=4033697 width=244)
-> Seq Scan on address_normalization a (cost=0.00..159113.97 rows=4033697 width=244)
Улучшение производительности очевидно на плане, но мне интересно, ожидается ли последовательное сканирование без каких-либо фильтров.
Столбец в person_addresses был числовым (15), по адресу - bigint. Я изменил адрес address.address_id на числовой (15). Теперь план выглядит так. Hash Join (cost=343467.18..881125.47 rows=5256374 width=348) Hash Cond: (pa.address_id = a.address_id) -> Seq Scan on person_addresses pa (cost=0.00..147477.74 rows=5256374 width=104) -> Hash (cost=159113.97..159113.97 rows=4033697 width=244) -> Seq Scan on address_normalization a (cost=0.00..159113.97 rows=4033697 width=244) Нормально ли сканирование последовательностей в этих случаях? План кажется более эффективным
«если ожидается последовательное сканирование без фильтров» - абсолютно. Если вам нужны все строки, Seq Scan - очевидный и самый быстрый способ их получить. Причина, по которой был выбран Merge Join, заключалась в том, что два типа данных не приводили к одним и тем же значениям хэша, и, следовательно, хеш-соединение было невозможно.
@a_horse_with_no_name Это на самом деле ответ.





Итак, здесь есть два вопроса:
why did Postgres choose the (expensive) "Merge Join" in the first query?
Причина этого в том, что он не может использовать более эффективный «Хеш-соединение», потому что значения хеш-функции целое число и числовой будут разными. Но объединение слиянием требует, чтобы значения были отсортированы, и именно отсюда шаг «Сортировка» происходит в первом плане выполнения. Учитывая количество строк, «Вложенная петля» был бы еще дороже.
Второй вопрос:
I am wondering if the sequential scans are expected without any filters
Да, они ожидаются. Запрос извлекает все совпадающие строки из обеих таблиц, и наиболее эффективно это делается путем сканирования всех строк. Для сканирования индекса требуется около 2-3 операций ввода-вывода в строке, которые необходимо получить. Последовательное сканирование обычно требует менее одной операции ввода-вывода, поскольку один блок (который является наименьшей единицей, которую база данных считывает с диска) содержит несколько строк.
Вы можете запустить explain (analyze, buffers), чтобы увидеть, сколько «логических чтений» занимает каждый шаг.
Вы уверены, что оба столбца относятся к типу INTEGER?