sql >> Database teknologi >  >> RDS >> Sqlserver

Sætbaseret plan kører langsommere end skalarvurderet funktion med mange betingelser

Søgeordsudtrykket her er INLINE FUNKTIONER VÆRDIGT i TABEL . Du har to typer T-SQL-tablerede værdisatte funktioner:multi-sætning og inline. Hvis din T-SQL-funktion starter med en BEGIN-sætning, bliver det lort - skalar eller andet. Du kan ikke få en midlertidig tabel ind i en inline tabel værdisat funktion, så jeg går ud fra, at du gik fra skalar til mutli-sætning tabel værdisat funktion, som sandsynligvis vil være værre.

Din inline table-valued function (iTVF) skulle se nogenlunde sådan ud:

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;
 

Bemærk, at din DATEDIFF i den kode, du indsendte sætningen mangler datepart parameter. Hvis skulle ligne:

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

Går man lidt længere - det er vigtigt at forstå, hvorfor iTVF'er er bedre end T-SQL skalære brugerdefinerede funktioner. Det er ikke, fordi tabelværdierede funktioner er hurtigere end skalære værdisatte funktioner, det er fordi Microsofts implementering af T-SQL inline-funktioner er hurtigere end deres implementering af T-SQL-funktioner, der ikke er inline. Bemærk følgende tre funktioner, der gør det samme:

-- 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 til nogle eksempler på data og ydeevnetest:

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
 

Resultater:

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

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

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

Den skalære udf kørte i 2,7 sekunder, 41 sekunder for mtvf og 0,153 sekunder for iTVF. For at forstå hvorfor lad os se på de anslåede udførelsesplaner:

Du kan ikke se dette, når du ser på den faktiske udførelsesplan, men med den skalære udf og mtvf kalder optimizeren en dårligt udført underrutine for hver række; det gør iTVF ikke. Citerer Paul Whites karriereskift artikel om ANSØG Paul skriver:

Med andre ord, iTVF'er gør det muligt at optimere for at optimere forespørgslen på måder, der bare ikke er mulige, når al den anden kode skal udføres. Et af mange andre eksempler på, hvorfor iTVF'er er overlegne, er, at de er den eneste af de tre førnævnte funktionstyper, der tillader parallelitet. Lad os køre hver funktion en gang til, denne gang med den faktiske udførelsesplan slået til og med sporingsflag 8649 (som fremtvinger en parallel udførelsesplan):

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

Udførelsesplaner:

De pile, du ser for iTVF's udførelsesplan, er parallelitet - alle dine CPU'er (eller lige så mange som din SQL-instans's MAXDOP indstillinger tillader) at arbejde sammen. T-SQL scalar og mtvf UDF'er kan ikke gøre det. Når Microsoft introducerer inline skalar UDF'er, vil jeg foreslå dem til det, du laver, men indtil da:hvis ydeevne er det, du leder efter, er inline den eneste vej at gå, og for det er iTVF'er det eneste spil i byen.

Bemærk, at jeg løbende lagde vægt på T-SQL når vi taler om funktioner... CLR Scalar og Table værdisatte funktioner kan være fint, men det er et andet emne.




  1. Indlæs data i fil, forskel mellem Windows og Linux

  2. Returner tilfældige resultater (rækkefølge efter rand() )

  3. MySQL JOIN returnerer NULL-felter

  4. DateTime's repræsentation i millisekunder?