Как избежать последовательного сканирования в запросе PostgreSQL - PullRequest
0 голосов
/ 12 апреля 2020

У меня есть 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.

1 Ответ

1 голос
/ 12 апреля 2020

Поскольку вы заранее знаете, когда хотите это сделать, просто выполните set enable_seqscan to off перед выполнением запроса и сбросьте его после.

...