クエリに特定のプランを使用するようにSQL Serverをだます方法があるかどうかを確認しようとしています。
1.環境
異なるプロセス間で共有されるデータがあるとします。したがって、多くのスペースをとるいくつかの実験結果があるとします。その後、各プロセスについて、使用する実験結果の年/月を特定します。
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
これで、すべてのプロセスについて、テーブルにパラメーターが保存されました
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.テストデータ
いくつかのテストデータを追加しましょう。
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.結果の取得
現在、次の方法で実験結果を取得するのは非常に簡単@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
計画は素晴らしく、並行しています:
select
calculated_number,
count(*)
from dbo.f_GetSharedData(2014, 4)
group by
calculated_number
クエリ0プラン
4.問題
しかし、データの使用をもう少し一般的にするために、別の機能が必要です- dbo.f_GetSharedDataBySession(@session_id int)
。したがって、簡単な方法は、スカラー関数を作成し、変換することです@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
これで、関数を作成できます。
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
クエリ1プラン
データアクセスを実行するスカラー関数がプラン全体をシリアルにするため、プランはもちろんパラレルではないことを除いて同じです。
そこで、スカラー関数の代わりにサブクエリを使用するなど、いくつかの異なるアプローチを試しました。
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
クエリ2プラン
またはを使用して 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
クエリ3プラン
しかし、このクエリを、スカラー関数を使用するクエリほど優れたものにする方法を見つけることはできません。
いくつかの考え:
- 基本的には、SQL Serverに特定の値を事前に計算してから定数としてさらに渡すように何らかの方法で指示できるようにすることです。
- 中間実体化のヒントがあれば便利です。いくつかのバリアント(topを含むマルチステートメントTVFまたはcte)をチェックしましたが、これまでのところスカラー関数を持つものほど良い計画はありません
- SQL Server 2017- Froid:Relational Databaseでの命令型プログラムの最適化の今後の改善について知っていますが、それが役立つかどうかはわかりません。ただし、ここで間違っていることが証明されていれば良かったでしょう。
追加情報
(テーブルから直接データを選択するのではなく)関数を使用し@session_id
ています。これは、通常はパラメーターとして使用されるさまざまなクエリで使用する方がはるかに簡単だからです。
実際の実行時間を比較するように頼まれました。この特定の場合
- クエリ0は約500ミリ秒実行されます
- クエリ1は約1500ms実行されます
- クエリ2は約1500ms実行されます
- クエリ3は約2000ミリ秒実行されます。
プラン#2にはシークの代わりにインデックススキャンがあり、ネストループの述語によってフィルターされます。プラン#3はそれほど悪くはありませんが、それでもプラン#0よりも多くの作業を行い、動作が遅くなります。
これdbo.Params
はめったに変更されず、通常は1〜200行程度であり、2000行を超えることはないと想定してみましょう。現在は約10列であり、あまり頻繁に列を追加することはありません。
Paramsの行数は固定されていないため、すべて@session_id
の行に行があります。そこにある列の数は固定されていませんdbo.f_GetSharedData(@experiment_year int, @experiment_month int)
。これはどこからでも呼び出したくない理由の1つです。そのため、このクエリに新しい列を内部で追加できます。何らかの制限がある場合でも、これに関する意見/提案を聞いてうれしいです。