У меня есть 3-уровневая настройка таблицы master-detail-subdetail.
- master: 100 строк
- detail: 300 000 строк, от 5 до 60 000 деталей на главную строку , в среднем 3000 деталей
- субдеталей: 30 000 000 строк, около 100 субдеталей на строку деталей и до 2М на мастер *
Когда я выполняю запрос, чтобы получить все субдетали для одного master, PostgreSQL всегда хочет выполнять параллельное последовательное сканирование на subdetail, даже когда я заранее знаю (из-за денормализации отсчетов), что для выбранного мной мастера есть только 200k или около того строк, которые нужно извлечь. Я могу контролировать, хочу ли я выполнить запрос или нет на уровне приложения, основываясь на статистике приложения подсчета деталей.
Я хочу заставить PostgreSQL присоединиться к master -> detail -> subdetail
в этом порядке, а не hash(master -> detail) -> parallel_scan(subdetail)
, что и делает. Я выдаю запрос только тогда, когда заранее знаю, что он будет выполнен в разумные сроки; хотя сканирование всех субдеталей занимает слишком много времени.
Чтобы сделать этот бетон, у меня может быть SQL примерно так:
select subdetail.kind, count(*)
from subdetail
join detail on detail.id = subdetail.detail_id
join master on master.id = detail.master_id
where master.name = 'foo'
group by 1
Я получаю план, подобный этому:
Finalize GroupAggregate (cost=841664.47..841665.23 rows=3 width=10) (actual time=34452.098..34452.100 rows=2 loops=1)
Group Key: subdetail.kind
Buffers: shared hit=1813 read=216528 dirtied=81 written=19
-> Gather Merge (cost=841664.47..841665.17 rows=6 width=10) (actual time=34452.089..34454.982 rows=6 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=5435 read=648798 dirtied=229 written=43
-> Sort (cost=840664.45..840664.46 rows=3 width=10) (actual time=34447.488..34447.489 rows=2 loops=3)
Sort Key: subdetail.kind
Sort Method: quicksort Memory: 25kB
Worker 0: Sort Method: quicksort Memory: 25kB
Worker 1: Sort Method: quicksort Memory: 25kB
Buffers: shared hit=5435 read=648798 dirtied=229 written=43
-> Partial HashAggregate (cost=840664.39..840664.42 rows=3 width=10) (actual time=34447.456..34447.457 rows=2 loops=3)
Group Key: subdetail.kind
Buffers: shared hit=5421 read=648798 dirtied=229 written=43
-> Hash Join (cost=3603.43..840029.99 rows=126881 width=2) (actual time=5002.903..34426.634 rows=76274 loops=3)
Hash Cond: (subdetail.detail_id = detail.id)
Buffers: shared hit=5421 read=648798 dirtied=229 written=43
-> Parallel Seq Scan on subdetail (cost=0.00..785977.10 rows=13195610 width=6) (actual time=0.053..32007.809 rows=10796566 loops=3)
Buffers: shared hit=5223 read=648798 dirtied=229 written=43
-> Hash (cost=3567.34..3567.34 rows=2887 width=4) (actual time=3.060..3.060 rows=2753 loops=3)
Buckets: 4096 Batches: 1 Memory Usage: 129kB
Buffers: shared hit=176
-> Nested Loop (cost=102.80..3567.34 rows=2887 width=4) (actual time=0.321..1.953 rows=2753 loops=3)
Buffers: shared hit=176
-> Seq Scan on master (cost=0.00..2.30 rows=1 width=4) (actual time=0.035..0.038 rows=1 loops=3)
Filter: ((name)::text = 'foo'::text)
Rows Removed by Filter: 103
Buffers: shared hit=3
-> Bitmap Heap Scan on detail (cost=102.80..3536.17 rows=2887 width=8) (actual time=0.278..1.095 rows=2753 loops=3)
Recheck Cond: (master_id = master.id)
Heap Blocks: exact=35
Buffers: shared hit=173
-> Bitmap Index Scan on index_detail_on_master_id_name (cost=0.00..102.08 rows=2887 width=0) (actual time=0.255..0.255 rows=2753 loops=3)
Index Cond: (master_id = master.id)
Buffers: shared hit=68
В MySQL я мог бы скорректировать свой запрос:
select subdetail.kind, count(*)
from master
straight_join detail on detail.master_id = master.id
straight_join subdetail on subdetail.detail_id = detail.id
where master.name = 'foo'
group by 1
Но PostgreSQL не имеет аналогичных подсказок.
Единственный способ, который я нашел для Чтобы быстро выполнить этот тип запроса, нужно получить отдельные идентификаторы отдельно, а затем вставить гигантские идентификаторы в запрос subdetail в гигантском IN
. Этот подход дает мне время запроса 131 мс против 31 с при последовательном сканировании для мастера с примерно 200 тыс. Субдеталей. Но, безусловно, должен быть лучший способ, когда приложение знает об ожидаемом наборе результатов из-за денормализации, поощрять PostgreSQL к выполнению плана, который может быть не самым эффективным для всех возможных запросов, но, как известно, более эффективным для выбранных параметров c, чем для разбивки запроса на уровне приложения.
Я запускаю PostgreSQL 11.6.