Saya mencoba untuk melihat apakah ada cara untuk mengelabui SQL Server untuk menggunakan rencana tertentu untuk kueri.
1. Lingkungan
Bayangkan Anda memiliki beberapa data yang dibagi di antara berbagai proses. Jadi, anggaplah kita memiliki beberapa hasil percobaan yang memakan banyak ruang. Kemudian, untuk setiap proses, kami tahu tahun / bulan hasil percobaan yang ingin kami gunakan.
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
Sekarang, untuk setiap proses kami memiliki parameter yang disimpan dalam tabel
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. Uji data
Mari kita tambahkan beberapa data uji:
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. Mengambil hasil
Sekarang, sangat mudah untuk mendapatkan hasil eksperimen dengan @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
Rencananya bagus dan paralel:
select
calculated_number,
count(*)
from dbo.f_GetSharedData(2014, 4)
group by
calculated_number
kueri 0 paket
4. Masalah
Tetapi, untuk membuat penggunaan data sedikit lebih umum, saya ingin memiliki fungsi lain - dbo.f_GetSharedDataBySession(@session_id int)
. Jadi, cara mudah adalah dengan membuat fungsi skalar, menerjemahkan @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
Dan sekarang kita dapat membuat fungsi kita:
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
rencana kueri 1
Paketnya sama, kecuali tentu saja, tidak paralel, karena fungsi skalar yang melakukan akses data membuat keseluruhan paket serial .
Jadi saya sudah mencoba beberapa pendekatan berbeda, seperti, menggunakan subqueries alih-alih fungsi skalar:
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
rencana kueri 2
Atau menggunakan 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
rencana kueri 3
Tetapi saya tidak dapat menemukan cara untuk menulis kueri ini sebaik yang menggunakan fungsi skalar.
Beberapa pemikiran:
- Pada dasarnya apa yang saya inginkan adalah bisa entah bagaimana memberitahu SQL Server untuk pra-menghitung nilai-nilai tertentu dan kemudian meneruskannya lebih lanjut sebagai konstanta.
- Apa yang bisa membantu adalah jika kita punya petunjuk materialisasi menengah . Saya telah memeriksa beberapa varian (multi-statement TVF atau cte dengan top), tetapi tidak ada rencana yang sebaik yang memiliki fungsi skalar sejauh ini.
- Saya tahu tentang peningkatan mendatang dari SQL Server 2017 - Froid: Optimalisasi Program Imperatif dalam Database Relasional. Namun, saya tidak yakin itu akan membantu. Akan lebih baik dibuktikan salah di sini.
Informasi tambahan
Saya menggunakan fungsi (daripada memilih data langsung dari tabel) karena jauh lebih mudah digunakan dalam berbagai pertanyaan, yang biasanya memiliki @session_id
sebagai parameter.
Saya diminta untuk membandingkan waktu eksekusi aktual. Dalam kasus khusus ini
- permintaan 0 berjalan untuk ~ 500 ms
- query 1 berjalan untuk ~ 1500ms
- query 2 berjalan untuk ~ 1500ms
- query 3 berjalan untuk ~ 2000 ms.
Plan # 2 memiliki pemindaian indeks, bukan pencarian, yang kemudian disaring oleh predikat pada loop bersarang. Plan # 3 tidak seburuk itu, tetapi masih bekerja lebih banyak dan bekerja lebih lambat dari rencana # 0.
Mari kita asumsikan itu dbo.Params
jarang diubah, dan biasanya memiliki sekitar 1-200 baris, tidak lebih dari, katakanlah 2000 yang pernah diharapkan. Sekarang sekitar 10 kolom dan saya tidak berharap untuk menambahkan kolom terlalu sering.
Jumlah baris di Params tidak tetap, jadi untuk setiap @session_id
akan ada satu baris. Jumlah kolom di sana tidak diperbaiki, itu salah satu alasan saya tidak ingin menelepon dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
dari mana-mana, jadi saya bisa menambahkan kolom baru ke kueri ini secara internal. Saya akan senang mendengar pendapat / saran tentang ini, bahkan jika itu memiliki beberapa batasan.