web-dev-qa-db-fra.com

UDF scalaire en ligne sous RCSI - Les résultats peuvent-ils différer

SQL Server 2019 présente Scalar UDF inlining , alias "Froid". Cette ".. incorpore [UDF scalaires] dans la requête SQL appelante."

Auparavant, les FDU scalaires s'exécutaient dans leur propre contexte d'exécution, distinct de celui de la requête environnante. Une conséquence de ceci est que sous l'isolement de cliché engagé en lecture (RCSI), la fonction peut voir un ensemble de valeurs différent de celui de la requête contenant ( link ).

Est-il possible qu'une requête contenant une fonction scalaire, lorsqu'elle s'exécute dans RCSI avec des écritures simultanées, puisse produire des résultats différents selon que la fonction est intégrée ou non?

11
Michael Green

Oui, une fonction en ligne peut afficher des résultats différents de son homologue en ligne (!?). Ce qui suit reproduit de manière fiable la situation sur ma machine (Windows 10, 4 cœurs + HT @ 2 GHz, 16 Go de RAM, SSD).

Configurez la base de données et la session pour utiliser l'isolation de capture instantanée de lecture (RCSI):

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

Ce tableau fournira un objet partagé sur lequel des charges de travail simultanées peuvent agir.

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

Ce tableau est destiné à capturer les résultats du test, révélant, espérons-le, des comportements divergents entre des fonctions intégrées et non:

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

Pour démontrer le comportement différent, je veux que deux fonctions soient exécutées dans un seul SELECT, dont l'une en ligne et l'autre non. Le documentation dit

Un UDF scalaire T-SQL peut être en ligne si .. l'UDF n'invoque aucune fonction intrinsèque .. comme GETDATE ()

Pour m'assurer qu'un UDF ne peut pas être en ligne, j'ajoute une référence à GETDATE. Notez que cette déclaration supplémentaire ne joue aucun rôle dans la logique de l'UDF, elle supprime simplement l'in-lining. (En effet, cette fonction pourrait être optimisée. Peut-être qu'une future version implémentera une telle optimisation?)

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

Pour référencer la table partagée, j'ai choisi arbitrairement d'utiliser SUM. Je crois, mais je n'ai pas testé, que toute autre technique qui fait apparaître des différences dans les lignes vues par les fonctions et le SELECT contenant (MIN, MAX, TOP (1)) ferait tout aussi bien.

Ensuite, je commence deux sessions. La première consiste à exécuter SELECT, la seconde à effectuer des écritures simultanées sur la table partagée.

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

J'ai défini l'exécution de la session en effectuant des écritures. Sur ma machine, il fonctionne pendant environ 24 secondes, ce qui est amplement le temps de passer à la session 1 (les lectures) et de la démarrer.

Pour une exécution sur 99 SELECT, il existe 12 cas où le mécanisme d'exécution en ligne et traditionnel renvoie des résultats différents. Dans tous les cas, la fonction intégrée renvoie le même résultat que la requête contenant (ce qui ne veut pas dire que ce test montre qu'un tel comportement est garanti).

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