У нас есть большое количество процедур, которые используют операторы 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 тысячах, но из-за того, что я буду называть проблемами с серверами вне нашего контроля, в одном случае, даже в нашем меньшем случае, второй запрос занял несколько минут до завершения, в то время как первый был почти мгновенным. Поэтому, конечно, у меня вопрос: почему для второго запроса был выбран другой план, и можем ли мы что-то с этим сделать?
Пожалуйста, кричите на меня, если я оставил что-то важное; это только мой второй вопрос здесь. Большое спасибо.