it-swarm.com.de

Inline Scalar UDF Under RCSI - Können die Ergebnisse abweichen

SQL Server 2019 führt Scalar UDF Inlining , auch bekannt als "Froid", ein. Dieses ".. bettet [skalare UDFs] in die aufrufende SQL-Abfrage ein."

Bisher wurden skalare UDFs in einem eigenen Ausführungskontext ausgeführt, der von den umgebenden Abfragen getrennt war. Eine Folge davon ist, dass die Funktion unter RCSI (Read Commit Snapshot Isolation) einen anderen Wertesatz sehen kann als die enthaltende Abfrage ( Link ).

Ist es möglich, dass eine Abfrage, die eine Skalarfunktion enthält, bei Ausführung in RCSI mit gleichzeitigen Schreibvorgängen unterschiedliche Ergebnisse liefert, je nachdem, ob die Funktion integriert ist oder nicht?

11
Michael Green

Ja, eine Inline-Funktion kann andere Ergebnisse anzeigen als ihre Outline-Funktion (!?). Im Folgenden wird die Situation auf meinem Computer zuverlässig wiedergegeben (Windows 10, 4 Kerne + HT bei 2 GHz, 16 GB RAM, SSD).

Konfigurieren Sie die Datenbank und die Sitzung für die Verwendung von RCSI (Read Committed Snapshot Isolation):

alter database Sandpit
set read_committed_snapshot on
with rollback immediate;
GO
set transaction isolation level read committed;
GO

Diese Tabelle gibt ein freigegebenes Objekt an, auf das gleichzeitige Workloads angewendet werden können.

drop table if exists t;
go
create table t(c int);
insert t(c) values (1);
go

Diese Tabelle soll die Ergebnisse des Tests erfassen und hoffentlich ein unterschiedliches Verhalten zwischen Funktionen aufzeigen, die inline sind und nicht:

drop table if exists #Out;
go
create table #Out(Base int, Old int, New int);
go

Um das unterschiedliche Verhalten zu demonstrieren, möchte ich, dass zwei Funktionen in einem einzigen SELECT ausgeführt werden, von denen eine inline und die andere nicht. Das Dokumentation sagt

Eine skalare T-SQL-UDF kann inline sein, wenn .. die UDF keine intrinsische Funktion aufruft .. wie GETDATE ()

Um sicherzustellen, dass eine UDF nicht inline sein kann, füge ich einen Verweis auf GETDATE hinzu. Beachten Sie, dass diese zusätzliche Anweisung keine Rolle in der Logik der UDF spielt, sondern lediglich das In-Lining unterdrückt. (In der Tat könnte diese Funktion wegoptimiert werden. Vielleicht wird eine zukünftige Version eine solche Optimierung implementieren?)

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

Um auf die gemeinsam genutzte Tabelle zu verweisen, habe ich mich willkürlich für die Verwendung von SUM entschieden. Ich glaube, habe aber noch nicht getestet, dass jede andere Technik, die Unterschiede in den Zeilen zeigt, die von den Funktionen und dem enthaltenen SELECT (MIN, MAX, TOP (1)) gesehen werden, genauso gut funktionieren würde.

Als nächstes beginne ich zwei Sitzungen. Die erste besteht darin, SELECT auszuführen, die zweite darin, gleichzeitig für die gemeinsam genutzte Tabelle zu schreiben.

-- 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

Ich habe festgelegt, dass die Sitzung ausgeführt wird, um Schreibvorgänge auszuführen. Auf meinem Computer läuft es ungefähr 24 Sekunden lang. Dies ist ausreichend Zeit, um zu Sitzung 1 (den Lesevorgängen) zu wechseln und sie zu starten.

Bei einem Lauf über 99 SELECTs gibt es 12 Fälle, in denen der Inline- und der traditionelle Ausführungsmechanismus unterschiedliche Ergebnisse zurückgeben. In jedem Fall gibt die Inline-Funktion das gleiche Ergebnis wie die enthaltene Abfrage zurück (was nicht heißt, dass dieser Test zeigt, dass ein solches Verhalten garantiert ist).

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