sql >> Databáze >  >> RDS >> Sqlserver

Nastavený plán běží pomaleji než skalární funkce s mnoha podmínkami

Klíčové slovo je zde INLINE FUNKCE HODNOTY TABULKY . Máte dva typy hodnotných funkcí v T-SQL:vícepříkazové a inline. Pokud vaše funkce T-SQL začíná příkazem BEGIN, pak to bude svinstvo – skalární nebo jiné. Do inline nelze vložit dočasnou tabulku funkce s hodnotou tabulky, takže předpokládám, že jste přešli ze skalární na funkci s hodnotou tabulky s více příkazy, která bude pravděpodobně horší.

Vaše funkce s hodnotou inline tabulky (iTVF) by měla vypadat nějak takto:

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;

Všimněte si, že v kódu, který jste zveřejnili, je vaše DATEDIFF v příkazu chybí datepart parametr. Pokud by mělo vypadat něco jako:

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

Jdeme trochu dále – je důležité pochopit, proč jsou iTVF lepší než skalárně oceňované uživatelsky definované funkce T-SQL. Není to proto, že funkce s hodnotou tabulky jsou rychlejší než funkce s hodnotou skalární, je to proto, že implementace inline funkcí T-SQL společnosti Microsoft je rychlejší než implementace funkcí T-SQL, které nejsou inline. Všimněte si následujících tří funkcí, které dělají totéž:

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

Nyní několik ukázkových dat a testu výkonu:

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

Výsledky:

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

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

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

Skalární udf běžel 2,7 sekundy, 41 sekund pro mtvf a 0,153 sekundy pro iTVF. Abychom pochopili, proč se podíváme na odhadované plány realizace:

Když se podíváte na skutečný plán provádění, nevidíte to, ale se skalárním udf a mtvf optimalizátor zavolá pro každý řádek nějaký špatně provedený podprogram; iTVF ne. Cituji změna kariéry Paula Whitea článek o APPLY Pavel píše:

Jinými slovy, iTVF umožňuje optimalizátoru optimalizovat dotaz způsoby, které prostě nejsou možné, když je třeba provést všechny ostatní kódy. Jedním z mnoha dalších příkladů, proč jsou iTVF lepší, je, že jsou jediným ze tří výše uvedených typů funkcí, které umožňují paralelismus. Spusťte každou funkci ještě jednou, tentokrát se zapnutým plánem Skutečného provádění a s traceflagem 8649 (který vynutí paralelní plán provádění):

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

Prováděcí plány:

Tyto šipky, které vidíte pro plán provádění iTVF, jsou paralelismus – všechny vaše CPU (nebo tolik jako MAXDOP vaší instance SQL nastavení umožňují) spolupracovat. Skalární T-SQL a mtvf UDF to neumí. Když Microsoft představí inline skalární UDF, pak bych je navrhoval pro to, co děláte, ale do té doby:pokud je výkon to, co hledáte, pak je inline jediným způsobem, jak jít, a proto jsou iTVF jedinou hrou. ve městě.

Všimněte si, že jsem neustále zdůrazňoval T-SQL když mluvíme o funkcích... CLR skalární a tabulkové funkce mohou být v pořádku, ale to je jiné téma.




  1. Jak MONTHNAME() funguje v MariaDB

  2. Připojení SAS JMP k Salesforce.com

  3. připojení k databázi nefunguje v jar, ale funguje v eclipse

  4. Jak spustit SQL ve skriptu shell