SQL Server 2019では スカラーUDFインライン化 、別名「Froid」が導入されています。これは「呼び出し元のSQLクエリに[スカラーUDF]を埋め込みます。」
以前は、スカラーUDFは周囲のクエリとは別の独自の実行コンテキストで実行されていました。これの1つの結果は、読み取りコミットスナップショット分離(RCSI)で、関数が、含まれているクエリが( link )とは異なる値のセットを参照する可能性があることです。
スカラー関数を含むクエリをRCSIで同時書き込みで実行すると、関数がインライン化されているかどうかによって、異なる結果が生成される可能性はありますか?
はい、インライン関数は、アウトライン(!?)の対応する関数とは異なる結果を表示できます。以下は、私のマシンの状況を確実に再現します(Windows 10、4コア+ HT @ 2GHz、16GB RAM、SSD)。
Read Committed Snapshot Isolation(RCSI)を使用するようにデータベースとセッションを構成します。
alter database Sandpit
set read_committed_snapshot on
with rollback immediate;
GO
set transaction isolation level read committed;
GO
このテーブルは、同時ワークロードが作用できる共有オブジェクトを提供します。
drop table if exists t;
go
create table t(c int);
insert t(c) values (1);
go
この表は、テストからの結果をキャプチャするためのものであり、インライン化されていない関数間の分岐動作を明らかにするものです。
drop table if exists #Out;
go
create table #Out(Base int, Old int, New int);
go
異なる動作を示すために、1つのSELECT内で2つの関数を実行する必要があります。1つはインラインで、もう1つはそうではありません。 ドキュメント は言う
UDFがGETDATE()などの組み込み関数を呼び出さない場合、スカラーT-SQL UDFをインラインにすることができます。
1つのUDFがインラインにならないようにするために、GETDATEへの参照を追加します。この追加のステートメントは、UDFのロジックには関与せず、単にインラインを抑制することに注意してください。 (確かに、この関数は最適化されてしまう可能性があります。おそらく将来のリリースでは、そのような最適化だけを実装するでしょうか?)
create or alter function dbo.Old_skool()
returns int
as
begin
declare @tot int = 0;
declare @d date = GETDATE(); -- inhibits in-lining
select @tot = SUM(C) from t;
return @tot;
end
go
create or alter function dbo.New_kid_on_the_block()
returns int
as
begin
declare @tot int = 0;
select @tot = SUM(C) from t;
return @tot;
end
go
共有テーブルを参照するために、SUMを使用することを任意に選択しました。関数とそれを含むSELECT(MIN、MAX、TOP(1))によって表示される行の違いを表面化する他の手法も同様に機能すると確信していますが、テストしていません。
次に、2つのセッションを開始します。 1つ目はSELECTを実行することで、2つ目は共有テーブルに対して同時書き込みを行うことです。
-- Session 1 for reads
set transaction isolation level read committed;
GO
truncate table #Out;
declare @c int = 0;
while @c < 99 -- large enough to exhibit the behaviour
begin
insert #Out(Base, Old, New)
select
c,
dbo.Old_skool(),
dbo.New_kid_on_the_block()
from t;
set @c += 1;
end
-- Session 2 for writes
declare @c int = 0;
while @c < 99999
begin
update t set c = c + 1;
set @c += 1;
end
書き込みを実行するセッションの実行を設定しました。私のマシンでは、約24秒間実行されます。これは、セッション1(読み取り)に切り替えて開始するのに十分な時間です。
99回以上のSELECTを1回実行すると、インラインと従来の実行メカニズムが異なる結果を返す12のインスタンスがあります。いずれの場合も、インライン関数は、含まれているクエリと同じ結果を返します(このテストがそのような動作が保証されていることを示すわけではありません)。
Base Old New
----------- ----------- -----------
1801 1802 1801
1803 1804 1803
1814 1815 1814
1841 1842 1841
1856 1857 1856
1857 1858 1857
1860 1861 1860
1861 1862 1861
1864 1865 1864
1883 1884 1883
1884 1885 1884
1890 1891 1890