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