добавление оператора case, где предложение приводит к неправильному выбору плана - PullRequest
0 голосов
/ 27 марта 2020

У нас есть большое количество процедур, которые используют операторы case в предложениях where. По сути, мы хотим запускать одни и те же процедуры в разное время, и мы устанавливаем «флаги» в базе данных, чтобы изменить поведение процедур в зависимости от времени, чтобы мы повторно обрабатывали наименьший объем данных, необходимый при выполнении этих процедур. Операторы case учитывают эти флаги.

Я думаю, что это ясно, как грязь, поэтому позвольте мне привести пример: иногда нам может потребоваться перестроить все содержимое таблицы, чтобы мы могли выполнить что-то как

insert into table_a select * from table_b

В других случаях мы хотим перестроить только строки, соответствующие определенным условиям:

insert into table_a select * from table_b where [business logic]

Запросы могут быть довольно сложными, и мы делаем это во многих различных процедурах, поэтому было бы плохой практикой переписывать все запросы для каждого сценария. Вместо этого мы делаем это:

update flag_table set flag = 't';

, затем в начале процедуры мы объявляем переменную -

declare _flag boolean;

. , .

_flag = (select a.flag from from flag_table a);

. , .

insert into table_a select * from table_b where case when _flag then [business logic] end

Очевидно, это грубое приближение к нашей практике; также я говорю «процедуры», но они postgres определяемые пользователем функции, просто чтобы быть точными.

Код ниже воссоздает проблему, которая вдохновила эту тему:

drop table if exists t_test; create temp table t_test as
    select  md5(random()::text) as test_text,
            (current_date - (random() * interval '5 years'))::date as date
    from generate_series (1,1000000);

drop table if exists t_sub; create temp table t_sub as
    select * from t_test order by random() limit 100000;

--efficient:
drop table if exists t_result; create temp table t_result as
    select  a.*
    from t_test a
    where   exists
        (
            select 1 from t_sub b where a.test_text = b.test_text and a.date between b.date - interval '6 months' and b.date + interval '6 months'
        );

--inefficient:
drop table if exists t_result; create temp table t_result as
    select  a.*
    from t_test a
    where   case when 1=1 then 
               exists
               (
                   select 1 from t_sub b where a.test_text = b.test_text and a.date between b.date - interval '6 months' and b.date + interval '6 months'
               )
            end;

Вот план запроса для запроса, помеченного как эффективный:

Hash Semi Join  (cost=7453.88..54133.48 rows=58801 width=36)
  Hash Cond: (a.test_text = b.test_text)
  Join Filter: ((a.date >= (b.date - '6 mons'::interval)) AND (a.date <= (b.date + '6 mons'::interval)))
  ->  Seq Scan on t_test a  (cost=0.00..40086.54 rows=1058418 width=36)
  ->  Hash  (cost=4011.54..4011.54 rows=105918 width=36)
        ->  Seq Scan on t_sub b  (cost=0.00..4011.54 rows=105918 width=36)

Вот план запроса для запроса, который я назвал неэффективным:

Seq Scan on t_test a  (cost=0.00..95755427.48 rows=529209 width=36)
  Filter: (SubPlan 1)
  SubPlan 1
    ->  Seq Scan on t_sub b  (cost=0.00..5335.51 rows=59 width=0)
          Filter: ((a.test_text = test_text) AND (a.date >= (date - '6 mons'::interval)) AND (a.date <= (date + '6 mons'::interval)))

Неэффективный план выполняется вечно (я дал в любом случае через 30 секунд). В нашем реальном случае мы не выполняем это на миллионах строк, а только на 10 тысячах, но из-за того, что я буду называть проблемами с серверами вне нашего контроля, в одном случае, даже в нашем меньшем случае, второй запрос занял несколько минут до завершения, в то время как первый был почти мгновенным. Поэтому, конечно, у меня вопрос: почему для второго запроса был выбран другой план, и можем ли мы что-то с этим сделать?

Пожалуйста, кричите на меня, если я оставил что-то важное; это только мой второй вопрос здесь. Большое спасибо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...