У нас есть конкретный запрос, который выполняется намного медленнее, когда внутри proc.Я должен добавить здесь, что он заключен в двухуровневый курсор.Однако оба курсора имеют набор результатов итерации, состоящий из одной строки.
Позвольте мне сначала заявить о том, что мы попробовали и потерпели неудачу:
- Предотвращение перехвата параметров с помощью option (Recompile) иoption (optiizes for (@var UNKNOWN)
- Этот поток . Переменные, которые кажутся проблемой, на самом деле являются локальными, а не параметрами proc.
Вот запрос, полученный изнутри proc / cursors.
select @tpdim1 = dim1, @tpdim2 = dim2, @typecalc = typecalc
from loyalty_policy where code=@loop2_loyalty_policy
Примечание: @ loop2_loyalty_policy - это переменная, взятая из результата внутреннего курсора, и имеет одно значение. code
- это PK дляloyalty_policy
таблица. Таким образом, @ tpdim1 и @ tpdim2 имеют по одному значению каждое.
SET STATISTICS PROFILE ON
SET STATISTICS xml on
insert into @tbl_loyal_loop2 (cnt, store, map, pda, insdate, line, item, loyalty_policy_data, loyal_calc, loyalty_policy)
select @cnt, t.store, t.map, t.pda, t.insdate, t.line, t.item, ld.tab_id,
case @typecalc
when 1 then convert(bigint,round(isnull(t.valueFromTran,0.00) * ld.value , 0 ) )
when 2 then convert(bigint,round(isnull(t.netvalue,0.00) * ld.value , 0 ) )
when 3 then convert(bigint,isnull(t.qty,0) * ld.value )
when 4 then convert(bigint,round(isnull(t.valueFromPrice2,0.00) * ld.value , 0 ) )
when 5 then convert(bigint,round(isnull(t.valueFromPrice3,0.00) * ld.value , 0 ) )
when 6 then convert(bigint,round(isnull(t.valueFromPrice4,0.00) * ld.value , 0 ) )
else 0 end
,@loop2_loyalty_policy
from loyalty_policy_data ld-- with (index=ind_loyalty_policy_02)
inner join #tbl_data t on t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
where ld.loyalty_policy = @loop2_loyalty_policy
and ld.tdateactive >= @from_rundate and ld.fdateactive <= @to_rundate
and t.dbupddate > @loop1_dbupddate
and
case when @tpdim1 is null then ''
else
case @tpdim1
when 'STORE' then t.store when 'BRAND' then t.brand when 'CAT1' then t.cat1 when 'CAT2' then t.cat2 when 'CAT3' then t.cat3 when 'ITEM' then t.item
when 'CUSTGROUP' then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
when 'CUSTOMER' then @customer
else '' end
end
= case when @tpdim1 is null then '' else ld.dim1 end
and
case when @tpdim2 is null then ''
else
case @tpdim2
when 'STORE' then t.store when 'BRAND' then t.brand when 'CAT1' then t.cat1 when 'CAT2' then t.cat2 when 'CAT3' then t.cat3 when 'ITEM' then t.item
when 'CUSTGROUP' then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
when 'CUSTOMER' then @customer
else '' end
end
= case when @tpdim2 is null then '' else ld.dim2 end
SET STATISTICS xml off
SET STATISTICS XML
для вышеуказанных возвратов этот план .
При попытке его отладки мы изолировали запрос в следующей форме (здесь вы также можете увидеть, как создается таблица #a, которая имеет точно такие же данные, что и предыдущая #tbl_data):
drop table #a;
select dt.dbupddate, dt.insdate, dt.map, dt.pda, pt.line, pt.item,
( pt.exp_qty - pt.imp_qty) as qty,
( pt.exp_value + pt.imp_value ) as netvalue,
( (document.exp_val - document.imp_val) * (pt.netvalue - pt.vat_value) ) as valueFromTran,
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price2,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice2,
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price3,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice3,
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price4,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice4,
dt.store, item.brand, item.cat1, item.cat2, item.cat3, customer.custgroup, customer.custgroup2, customer.custgroup3
into #a
from document with (nolock)
inner join dt with (nolock) on dt.doccode = document.code
inner join store with (nolock) on store.code = dt.store and store.calc_loyal = 1
inner join customer with (nolock) on customer.code = dt.customer
inner join pt with (nolock) on dt.map = pt.map and dt.pda=pt.pda
inner join item with (nolock) on item.code = pt.item and item.itemtype in (select code from itemtype with (nolock) where vsales = 1)
where dt.canceled = 0 and document.is_opposite = 0 and document.type = 3 and dt.customer=N'EL4444444'
and dt.insdate >= '20180109' and dt.insdate <= '20190108' ;
SET STATISTICS PROFILE ON
select t.store, t.map, t.pda, t.insdate, t.line, t.item, ld.tab_id,
case 4
when 1 then convert(bigint,round(isnull(t.valueFromTran,0.00) * ld.value , 0 ) )
when 2 then convert(bigint,round(isnull(t.netvalue,0.00) * ld.value , 0 ) )
when 3 then convert(bigint,isnull(t.qty,0) * ld.value )
when 4 then convert(bigint,round(isnull(t.valueFromPrice2,0.00) * ld.value , 0 ) )
when 5 then convert(bigint,round(isnull(t.valueFromPrice3,0.00) * ld.value , 0 ) )
when 6 then convert(bigint,round(isnull(t.valueFromPrice4,0.00) * ld.value , 0 ) )
else 0 end
,'003'
--select count(*)
from loyalty_policy_data ld with (index=ind_loyalty_policy_02)
inner join #a t on t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
where ld.loyalty_policy = '003'
--and ld.tdateactive >= '20180109' and ld.fdateactive <= '20190108'
and t.dbupddate > '20000101'
and
case when 'CUSTOMER' is null then ''
else
case 'CUSTOMER'
when 'STORE' then t.store when 'BRAND' then t.brand when 'CAT1' then t.cat1 when 'CAT2' then t.cat2 when 'CAT3' then t.cat3 when 'ITEM' then t.item
when 'CUSTGROUP' then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
when 'CUSTOMER' then 'EL0134366'
else '' end
end
= case when 'CUSTOMER' is null then '' else ld.dim1 end
and
case when 'BRAND' is null then ''
else
case 'BRAND'
when 'STORE' then t.store when 'BRAND' then t.brand when 'CAT1' then t.cat1 when 'CAT2' then t.cat2 when 'CAT3' then t.cat3 when 'ITEM' then t.item
when 'CUSTGROUP' then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
when 'CUSTOMER' then 'EL0134366'
else '' end
end
= case when 'BRAND' is null then '' else ld.dim2 end
SET STATISTICS PROFILE off
И здесь - это план выполнения. Он работает ОЧЕНЬ быстроэр.
Почему такая огромная разница?Из моих ограниченных знаний об анализе выполнения я заметил
- Первый (медленный) запрос операции
index spool
имеет оценочные строки ~ 9700, но фактические строки - 3 миллиона. - Во втором запросе использовалось много операций с параллелизмом
- Единственная «реальная» разница, которую я вижу во втором запросе, - это подставляемые вручную значения значений @ tpdim1 и @ tpdim2. Конечно, , когда мы вошли в код процедуры первого запроса и заменили @ tpdim1 & @ tpdim2 на единственные значения, которые они должны получить, он работал так же быстро, как второй запрос .
Не могли бы вы объяснить эту разницу и предложить несколько советов по исправлению процедуры?
Редактировать: Как рекомендовал Смеющийся Вергилий, я заменил литералы во втором запросе переменнымиранее заявлено, и снова он работает медленно!
Редактировать 2: У меня есть дополнительная информация о дальнейших исследованиях.
Сначала я выделил проблему в этой строке:
case when @tpdim1 is null then ''
<- Используется медленный план </p>
case when 'CUSTOMER' is null then ''
<- Используется быстрый план </p>
Это верно для специального запроса, не нужно беспокоиться о spcs и / иликурсоры.
Это продолжает происходить, даже если я изменяю код на рекомендованную динамическую структуру, где.
Я еще не создал никаких данных выборки, но важную информацию (как можно видеть впланы), что loyalty_policy_data
имеет около 720k строк, если мы фильтруем только по loyalty_policy = @loop2_loyalty_policy
.Тем не менее, если мы оценим условие @ tpdim1, которое по существу равно dim1 = N'EL0134366 ', возвращаемых строк будет только 4.
Разница в плане, тогда, когда это условие оценивается в отношенииусловия проверки даты.
В быстром плане он оценивается первым - при поиске индекса для значения политики лояльности он добавляет предикат (без поиска).Хотя этот предикат находится за пределами индекса, возвращаемых строк равно 4, а все остальные операторы имеют «логические» размеры.
В отличие от этого, медленный план болезненно игнорирует этот предикат, пока не станет слишком поздно.Если я понял правильно, он создает вложенные циклы на loyalty_policy_data в качестве внешней таблицы (что безумие).Он передает необходимые столбцы в качестве внешних ссылок.Для каждого такого кортежа папка индекса сканирует таблицу # (~ 1k строк), находит около 250 результатов и передает ее фильтру, который окончательно выполняет фильтрацию tpdim1.Таким образом, 250 * 700 тыс. Строк передаются оператору фильтра.
Так что теперь я думаю, что знаю, что происходит.Но я не могу понять, почему.