sql >> Database >  >> RDS >> Sqlserver

Op set gebaseerd plan werkt langzamer dan scalair gewaardeerde functie met veel voorwaarden

De zoekwoordterm hier is INLINE TABEL GEWAARDEERDE FUNCTIES . U hebt twee soorten T-SQL-tabelwaardige functies:multi-statement en inline. Als je T-SQL-functie begint met een BEGIN-instructie, dan wordt het onzin - scalair of anderszins. U kunt geen tijdelijke tabel in een inline krijgen functie met tabelwaarde, dus ik neem aan dat je van scalaire functie naar tabelwaardefunctie met meerdere instructies bent gegaan, wat waarschijnlijk erger zal zijn.

Uw inline tabel gewaardeerde functie (iTVF) zou er ongeveer zo uit moeten zien:

CREATE FUNCTION [dbo].[Compute_value]
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue = 
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(<unit of measurement>,GETDATE(),'1/1/2000')/365)))
  END
GO;

Merk op dat, in de code die je hebt gepost, je DATEDIFF statement mist het datepart parameter. Als het er ongeveer zo uit zou moeten zien:

@x int = DATEDIFF(DAY, GETDATE(),'1/1/2000')   

Een beetje verder gaan - het is belangrijk om te begrijpen waarom iTVF's beter zijn dan T-SQL scalair gewaardeerde, door de gebruiker gedefinieerde functies. Het is niet omdat functies met tabelwaarde sneller zijn dan functies met schaalwaarde, maar omdat Microsoft's implementatie van T-SQL inline-functies sneller is dan hun implementatie van T-SQL-functies die niet inline zijn. Let op de volgende drie functies die hetzelfde doen:

-- Scalar version
CREATE FUNCTION dbo.Compute_value_scalar
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS FLOAT
AS
BEGIN
    IF @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 
    RETURN 0

    IF @bravo IS NULL OR @bravo <= 0
        RETURN 100

    IF (@charle + @delta) / @bravo <= 0
        RETURN 100
    DECLARE @x int = DATEDIFF(dd, GETDATE(),'1/1/2000')     
    RETURN @alpha * POWER((100 / @delta), (-2 * POWER(@charle * @bravo, @x/365)))
END
GO

-- multi-statement table valued function 
CREATE FUNCTION dbo.Compute_value_mtvf
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS  @sometable TABLE (newValue float) AS 
    BEGIN
    INSERT @sometable VALUES
(
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
  END
)
RETURN;
END
GO

-- INLINE table valued function
CREATE FUNCTION dbo.Compute_value_itvf
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue = 
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
  END
GO

Nu voor enkele voorbeeldgegevens en prestatietests:

SET NOCOUNT ON;
CREATE TABLE #someTable (alpha FLOAT, bravo FLOAT, charle FLOAT, delta FLOAT);
INSERT #someTable
SELECT TOP (100000)
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1, 
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a, sys.all_columns b;

PRINT char(10)+char(13)+'scalar'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t;

PRINT DATEDIFF(ms, @st, getdate());
GO

PRINT char(10)+char(13)+'mtvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f;

PRINT DATEDIFF(ms, @st, getdate());
GO

PRINT char(10)+char(13)+'itvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f;

PRINT DATEDIFF(ms, @st, getdate());
GO

Resultaten:

scalar
------------------------------------------------------------
2786

mTVF
------------------------------------------------------------
41536

iTVF
------------------------------------------------------------
153

De scalaire udf liep gedurende 2,7 seconden, 41 seconden voor de mtvf en 0,153 seconden voor de iTVF. Laten we eens kijken naar de geschatte uitvoeringsplannen om te begrijpen waarom:

Je ziet dit niet als je naar het daadwerkelijke uitvoeringsplan kijkt, maar met de scalaire udf en mtvf roept de optimizer een slecht uitgevoerde subroutine aan voor elke rij; de iTVF niet. Citaat van Paul White's loopbaanverandering artikel over TOEPASSEN Paul schrijft:

Met andere woorden, iTVF's stellen de optimizer in staat om de query te optimaliseren op manieren die gewoon niet mogelijk zijn wanneer al die andere code moet worden uitgevoerd. Een van de vele andere voorbeelden van waarom iTVF's superieur zijn, is dat ze de enige van de drie bovengenoemde functietypes zijn die parallellisme mogelijk maken. Laten we elke functie nog een keer uitvoeren, deze keer met het werkelijke uitvoeringsplan ingeschakeld en met traceflag 8649 (die een parallel uitvoeringsplan afdwingt):

-- don't need so many rows for this test
TRUNCATE TABLE #sometable;
INSERT #someTable 
SELECT TOP (10)
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1, 
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a;

DECLARE @x float;

SELECT TOP (10) @x = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t
ORDER BY dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
OPTION (QUERYTRACEON 8649);

SELECT TOP (10)  @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);

SELECT @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);

Uitvoeringsplannen:

Die pijlen die je ziet voor het uitvoeringsplan van de iTVF is parallellisme - al je CPU's (of zoveel als de MAXDOP van je SQL-instantie instellingen toestaan) samenwerken. T-SQL scalaire en mtvf UDF's kunnen dat niet. Wanneer Microsoft inline scalaire UDF's introduceert, zou ik die voorstellen voor wat u doet, maar tot die tijd:als prestaties zijn wat u zoekt, dan is inline de enige manier om te gaan en daarvoor zijn iTVF's de enige game in de stad.

Merk op dat ik voortdurend de nadruk legde op T-SQL als we het over functies hebben... CLR Scalaire en tabelwaardefuncties kunnen prima zijn, maar dat is een ander onderwerp.




  1. Hoe schrijf ik XML naar een bestand (PL/SQL)?

  2. SQLite LIKE

  3. Hoe Sequelize enkelvoudige tabelnamen te gebruiken

  4. Tekencoderingsprobleem