web-dev-qa-db-ja.com

RCSIでのインラインスカラーUDF-結果は異なる可能性があります

SQL Server 2019では スカラーUDFインライン化 、別名「Froid」が導入されています。これは「呼び出し元のSQLクエリに[スカラーUDF]を埋め込みます。」

以前は、スカラーUDFは周囲のクエリとは別の独自の実行コンテキストで実行されていました。これの1つの結果は、読み取りコミットスナップショット分離(RCSI)で、関数が、含まれているクエリが( link )とは異なる値のセットを参照する可能性があることです。

スカラー関数を含むクエリをRCSIで同時書き込みで実行すると、関数がインライン化されているかどうかによって、異なる結果が生成される可能性はありますか?

11
Michael Green

はい、インライン関数は、アウトライン(!?)の対応する関数とは異なる結果を表示できます。以下は、私のマシンの状況を確実に再現します(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
6
Michael Green