Sorgu için belirli bir planı kullanmak için SQL Server kandırmak için bir yol olup olmadığını görmeye çalışıyorum.
1. Çevre
Farklı süreçler arasında paylaşılan bazı verileriniz olduğunu düşünün. Diyelim ki çok yer kaplayan bazı deney sonuçlarımız var. Ardından, her işlem için hangi yıl / ay deneme sonucunu kullanmak istediğimizi biliyoruz.
if object_id('dbo.SharedData') is not null
drop table SharedData
create table dbo.SharedData (
experiment_year int,
experiment_month int,
rn int,
calculated_number int,
primary key (experiment_year, experiment_month, rn)
)
go
Şimdi, her işlem için tabloya kaydedilmiş parametrelerimiz var
if object_id('dbo.Params') is not null
drop table dbo.Params
create table dbo.Params (
session_id int,
experiment_year int,
experiment_month int,
primary key (session_id)
)
go
2. Test verileri
Biraz test verisi ekleyelim:
insert into dbo.Params (session_id, experiment_year, experiment_month)
select 1, 2014, 3 union all
select 2, 2014, 4
go
insert into dbo.SharedData (experiment_year, experiment_month, rn, calculated_number)
select
2014, 3, row_number() over(order by v1.name), abs(Checksum(newid())) % 10
from master.dbo.spt_values as v1
cross join master.dbo.spt_values as v2
go
insert into dbo.SharedData (experiment_year, experiment_month, rn, calculated_number)
select
2014, 4, row_number() over(order by v1.name), abs(Checksum(newid())) % 10
from master.dbo.spt_values as v1
cross join master.dbo.spt_values as v2
go
3. Sonuçlar getiriliyor
Şimdi, aşağıdakilerle deneme sonuçları almak çok kolay @experiment_year/@experiment_month
:
create or alter function dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.SharedData as d
where
d.experiment_year = @experiment_year and
d.experiment_month = @experiment_month
)
go
Plan güzel ve paralel:
select
calculated_number,
count(*)
from dbo.f_GetSharedData(2014, 4)
group by
calculated_number
sorgu 0 planı
4. Sorun
Ancak, verilerin kullanımını biraz daha genel hale getirmek için başka bir işleve sahip olmak istiyorum dbo.f_GetSharedDataBySession(@session_id int)
. Yani, çevirmenin basit bir yolu, skaler fonksiyonlar oluşturmaktır @session_id
-> @experiment_year/@experiment_month
:
create or alter function dbo.fn_GetExperimentYear(@session_id int)
returns int
as
begin
return (
select
p.experiment_year
from dbo.Params as p
where
p.session_id = @session_id
)
end
go
create or alter function dbo.fn_GetExperimentMonth(@session_id int)
returns int
as
begin
return (
select
p.experiment_month
from dbo.Params as p
where
p.session_id = @session_id
)
end
go
Ve şimdi fonksiyonumuzu yaratabiliriz:
create or alter function dbo.f_GetSharedDataBySession1(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.f_GetSharedData(
dbo.fn_GetExperimentYear(@session_id),
dbo.fn_GetExperimentMonth(@session_id)
) as d
)
go
sorgu 1 planı
Veri erişimi gerçekleştiren skaler fonksiyonlar tüm planı seri hale getirdiğinden, plan elbette paralel olmadığı dışında aynıdır .
Bu nedenle, skaler fonksiyonlar yerine alt sorguları kullanmak gibi birkaç farklı yaklaşım denedim:
create or alter function dbo.f_GetSharedDataBySession2(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.f_GetSharedData(
(select p.experiment_year from dbo.Params as p where p.session_id = @session_id),
(select p.experiment_month from dbo.Params as p where p.session_id = @session_id)
) as d
)
go
sorgu 2 planı
Veya kullanarak cross apply
create or alter function dbo.f_GetSharedDataBySession3(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.Params as p
cross apply dbo.f_GetSharedData(
p.experiment_year,
p.experiment_month
) as d
where
p.session_id = @session_id
)
go
sorgu 3 planı
Ama bu sorguyu skaler fonksiyonları kullanan kadar iyi olmak için bir yol bulamıyorum.
Birkaç düşünce:
- Temelde ne istiyorum bir şekilde SQL Server belirli değerleri önceden hesaplamak ve sonra sabit olarak daha fazla iletmek için söylemek mümkün olmaktır.
- Yararlı olabilecek şey, bazı ara materyalizasyon ipucumuz olması. Birkaç varyantı kontrol ettim (çoklu ifade TVF veya üstte cte), ancak şimdiye kadar skaler fonksiyonları olan plan kadar iyi değil
- SQL Server 2017 - Froid: İlişkisel Veritabanında Zorunlu Programların Optimizasyonu'nun geleceğini biliyorum . Yine de yardımcı olacağından emin değilim. Yine de burada yanlış ispatlanmış olmak güzel olurdu.
Ek bilgi
Genellikle @session_id
parametre olarak sahip birçok farklı sorgularda kullanmak çok daha kolay olduğu için (doğrudan tablolardan veri seçmek yerine) bir işlev kullanıyorum .
Gerçek yürütme sürelerini karşılaştırmam istendi. Bu özel durumda
- 0 sorgusu ~ 500 ms boyunca çalışır
- sorgu 1 ~ 1500ms için çalışır
- sorgu 2 ~ 1500ms çalışır
- sorgu 3 ~ 2000ms için çalışır.
Plan # 2, arama yerine bir indeks taramasına sahiptir ve daha sonra iç içe döngülerdeki tahminlerle filtrelenir. Plan # 3 o kadar da kötü değil, ama yine de daha fazla iş yapıyor ve # 0 planını daha yavaş çalışıyor.
Diyelim ki dbo.Params
nadiren değişti ve genellikle yaklaşık 1-200 sıra var, daha fazla değil, diyelim ki 2000 bekleniyor. Şimdi 10 sütun civarında ve çok sık sütun eklemeyi beklemiyorum.
Params'daki satır sayısı sabit değildir, bu nedenle her biri @session_id
için bir satır olacaktır. Orada sütun sayısı sabit değil dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
, her yerden aramak istemem nedenlerinden biri, bu nedenle bu sorguya dahili olarak yeni sütun ekleyebilirim. Bazı kısıtlamaları olsa bile, bu konuda herhangi bir görüş / öneri duymaktan memnuniyet duyarım.