Может ли PostgreSQL 12 выполнять сокращение раздела во время выполнения с помощью подзапроса, возвращающего список? - PullRequest
3 голосов
/ 09 апреля 2020

Я пытаюсь воспользоваться преимуществами разбиения в одном случае: У меня есть таблица "события", которая разделена на список по полю "dt_pk", который является внешним ключом к таблице "даты".

-- Schema
drop schema if exists test cascade;
create schema test;

-- Tables
create table if not exists test.dates (
  id bigint primary key,
  dt date   not null
);

create sequence test.seq_events_id;

create table if not exists test.events
(
  id          bigint  not null,
  dt_pk       bigint  not null, 
  content_int bigint,

  foreign key (dt_pk) references test.dates(id) on delete cascade,
  primary key (dt_pk, id)
)
partition by list (dt_pk);

-- Partitions
create table test.events_1 partition of test.events for values in (1);
create table test.events_2 partition of test.events for values in (2);
create table test.events_3 partition of test.events for values in (3);

-- Fill tables
insert into test.dates (id, dt)
select id, dt
from (
  select 1 id, '2020-01-01'::date as dt
union all
  select 2 id, '2020-01-02'::date as dt
union all
  select 3 id, '2020-01-03'::date as dt
) t;

do $$
declare
  dts record;
begin  
  for dts in (
    select id
    from test.dates
  ) loop
    for k in 1..10000 loop    
      insert into test.events (id, dt_pk, content_int)
      values (nextval('test.seq_events_id'), dts.id, random_between(1, 1000000));
    end loop;
    commit;
  end loop;
end;
$$;

vacuum analyze test.dates, test.events;

Я хочу запустить выбрать, как это:

select *
from test.events e
  join test.dates d on e.dt_pk = d.id
where d.dt between '2020-01-02'::date and '2020-01-03'::date;

Но в этом случае обрезка разделов не работает. Понятно, у меня нет константы для ключа раздела. Но из документации я знаю, что во время выполнения происходит сокращение раздела, которое работает со значением, полученным из подзапроса:

Удаление раздела может выполняться не только во время планирования данный запрос, но и во время его выполнения. Это полезно, поскольку позволяет сократить количество разделов, когда предложения содержат выражения, значения которых неизвестны во время планирования запроса, например, параметры, определенные в операторе PREPARE, с использованием значения, полученного из подзапроса , или используя параметризованное значение на внутренней стороне вложенного соединения l oop.

Поэтому я переписываю свой запрос следующим образом, и я ожидал, что разделение будет сокращено:

select *
from test.events e
where e.dt_pk in (
  select d.id
  from test.dates d
  where d.dt between '2020-01-02'::date and '2020-01-03'::date
);

Но explain для этого выбора говорит:

Hash Join  (cost=1.07..833.07 rows=20000 width=24) (actual time=3.581..15.989 rows=20000 loops=1)
  Hash Cond: (e.dt_pk = d.id)
  ->  Append  (cost=0.00..642.00 rows=30000 width=24) (actual time=0.005..6.361 rows=30000 loops=1)
        ->  Seq Scan on events_1 e  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.005..1.104 rows=10000 loops=1)
        ->  Seq Scan on events_2 e_1  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.005..1.127 rows=10000 loops=1)
        ->  Seq Scan on events_3 e_2  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.008..1.097 rows=10000 loops=1)
  ->  Hash  (cost=1.04..1.04 rows=2 width=8) (actual time=0.006..0.006 rows=2 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        ->  Seq Scan on dates d  (cost=0.00..1.04 rows=2 width=8) (actual time=0.004..0.004 rows=2 loops=1)
              Filter: ((dt >= '2020-01-02'::date) AND (dt <= '2020-01-03'::date))
              Rows Removed by Filter: 1
Planning Time: 0.206 ms
Execution Time: 17.237 ms

Итак, мы читаем все разделы. Я даже пытался в планировщике использовать вложенное соединение l oop, потому что я прочитал в документации " параметризованное значение на внутренней стороне вложенного соединения l oop ", но это не сработало:

set enable_hashjoin to off;
set enable_mergejoin to off;

И снова:

Nested Loop  (cost=0.00..1443.05 rows=20000 width=24) (actual time=9.160..25.252 rows=20000 loops=1)
  Join Filter: (e.dt_pk = d.id)
  Rows Removed by Join Filter: 30000
  ->  Append  (cost=0.00..642.00 rows=30000 width=24) (actual time=0.008..6.280 rows=30000 loops=1)
        ->  Seq Scan on events_1 e  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.008..1.105 rows=10000 loops=1)
        ->  Seq Scan on events_2 e_1  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.008..1.047 rows=10000 loops=1)
        ->  Seq Scan on events_3 e_2  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.007..1.082 rows=10000 loops=1)
  ->  Materialize  (cost=0.00..1.05 rows=2 width=8) (actual time=0.000..0.000 rows=2 loops=30000)
        ->  Seq Scan on dates d  (cost=0.00..1.04 rows=2 width=8) (actual time=0.004..0.004 rows=2 loops=1)
              Filter: ((dt >= '2020-01-02'::date) AND (dt <= '2020-01-03'::date))
              Rows Removed by Filter: 1
Planning Time: 0.202 ms
Execution Time: 26.516 ms

Затем я заметил, что в каждом примере «обрезки раздела во время выполнения» я вижу только условие =, а не in. И это действительно работает следующим образом:

explain (analyze) select * from test.events e where e.dt_pk = (select id from test.dates where id = 2);

Append  (cost=1.04..718.04 rows=30000 width=24) (actual time=0.014..3.018 rows=10000 loops=1)
  InitPlan 1 (returns $0)
    ->  Seq Scan on dates  (cost=0.00..1.04 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=1)
          Filter: (id = 2)
          Rows Removed by Filter: 2
  ->  Seq Scan on events_1 e  (cost=0.00..189.00 rows=10000 width=24) (never executed)
        Filter: (dt_pk = $0)
  ->  Seq Scan on events_2 e_1  (cost=0.00..189.00 rows=10000 width=24) (actual time=0.004..2.009 rows=10000 loops=1)
        Filter: (dt_pk = $0)
  ->  Seq Scan on events_3 e_2  (cost=0.00..189.00 rows=10000 width=24) (never executed)
        Filter: (dt_pk = $0)
Planning Time: 0.135 ms
Execution Time: 3.639 ms

И вот мой последний вопрос: работает ли отсечение разделов во время выполнения только с подзапросом, возвращающим один элемент, или есть способ получить преимущества отсечения разделов с помощью подзапроса возвращает список?

И почему он не работает с вложенным соединением l oop, я понял что-то неправильно в словах:

Это включает в себя значения из подзапросов и значения из параметры времени выполнения, такие как параметры из параметризованных вложенных соединений l oop.

или " параметризованные вложенные соединения l oop " чем-то отличаются от обычных вложенных l oop присоединяется?

Ответы [ 2 ]

2 голосов
/ 09 апреля 2020

В вложенном соединении l oop нет сокращения разделов, поскольку разделенная таблица находится на внешней стороне, которая всегда сканируется полностью. Внутренняя сторона сканируется с помощью ключа соединения с внешней стороны в качестве параметра (следовательно, параметризованного сканирования), поэтому, если секционированная таблица находилась на внутренней стороне вложенного соединения l oop, может произойти отсечение раздела.

Удаление разделов с помощью IN списков может иметь место, если значения списков известны во время планирования:

EXPLAIN (COSTS OFF)
SELECT * FROM test.events WHERE dt_pk IN (1, 2);

                    QUERY PLAN                     
---------------------------------------------------
 Append
   ->  Seq Scan on events_1
         Filter: (dt_pk = ANY ('{1,2}'::bigint[]))
   ->  Seq Scan on events_2
         Filter: (dt_pk = ANY ('{1,2}'::bigint[]))
(5 rows)

Но не делается никаких попыток сгладить подзапрос, и PostgreSQL не использует сокращение разделов , даже если вы заставите разделенную таблицу быть на внутренней стороне (enable_material = off, enable_hashjoin = off, enable_mergejoin = off):

EXPLAIN (ANALYZE)
SELECT * FROM test.events WHERE dt_pk IN (SELECT 1 UNION SELECT 2);

                                                       QUERY PLAN                                                        
-------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.06..2034.09 rows=20000 width=24) (actual time=0.057..15.523 rows=20000 loops=1)
   Join Filter: (events_1.dt_pk = (1))
   Rows Removed by Join Filter: 40000
   ->  Unique  (cost=0.06..0.07 rows=2 width=4) (actual time=0.026..0.029 rows=2 loops=1)
         ->  Sort  (cost=0.06..0.07 rows=2 width=4) (actual time=0.024..0.025 rows=2 loops=1)
               Sort Key: (1)
               Sort Method: quicksort  Memory: 25kB
               ->  Append  (cost=0.00..0.05 rows=2 width=4) (actual time=0.006..0.009 rows=2 loops=1)
                     ->  Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.005..0.005 rows=1 loops=1)
                     ->  Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.001..0.001 rows=1 loops=1)
   ->  Append  (cost=0.00..642.00 rows=30000 width=24) (actual time=0.012..4.334 rows=30000 loops=2)
         ->  Seq Scan on events_1  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.011..1.057 rows=10000 loops=2)
         ->  Seq Scan on events_2  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.004..0.641 rows=10000 loops=2)
         ->  Seq Scan on events_3  (cost=0.00..164.00 rows=10000 width=24) (actual time=0.002..0.594 rows=10000 loops=2)
 Planning Time: 0.531 ms
 Execution Time: 16.567 ms
(16 rows)

Я не уверен, но это может быть потому, что таблицы настолько мал. Возможно, вы захотите попробовать с большими столами.

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

Если вас больше интересует, как это работает, чем мелких деталей, и вы еще не пробовали это: вы можете переписать запрос в что-то вроде

explain analyze select *
from test.dates d
  join test.events e on e.dt_pk = d.id
where 
d.dt between '2020-01-02'::date and '2020-01-03'::date
and e.dt_pk in (extract(day from '2020-01-02'::date)::int, 
                 extract(day from '2020-01-03'::date)::int); 

, что даст ожидаемое сокращение.

...