Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Планът, базиран на набор, работи по-бавно от функция със скаларни стойности с много условия

Ключовият термин тук е INLINE ФУНКЦИИ С ТАБЛИЧНА СТОЙНОСТ . Имате два типа T-SQL таблични стойностни функции:мулти-изрази и вградени. Ако вашата T-SQL функция започва с оператор BEGIN, тогава това ще бъде глупост - скаларна или друга. Не можете да поставите временна таблица във вградена функция с таблични стойности, така че предполагам, че сте преминали от скаларна към функция с таблични стойности с множество изрази, което вероятно ще бъде по-лошо.

Вашата вградена таблично стойностна функция (iTVF) трябва да изглежда по следния начин:

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;

Имайте предвид, че в публикувания от вас код вашият DATEDIFF в оператора липсва datepart параметър. Ако трябва да изглежда нещо като:

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

Продължавайки малко по-далеч - важно е да разберем защо iTVF са по-добри от дефинираните от потребителя функции със скаларна стойност на T-SQL. Не защото функциите с таблични стойности са по-бързи от функциите със скаларни стойности, а защото внедряването на T-SQL вградени функции на Microsoft е по-бързо от имплементирането на T-SQL функции, които не са вградени. Обърнете внимание на следните три функции, които правят едно и също нещо:

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

Сега за примерни данни и тест за ефективност:

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

Резултати:

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

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

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

Скаларният udf се изпълнява за 2,7 секунди, 41 секунди за mtvf и 0,153 секунди за iTVF. За да разберем защо, нека да разгледаме прогнозните планове за изпълнение:

Не виждате това, когато гледате действителния план за изпълнение, но със скаларните udf и mtvf, оптимизаторът извиква някаква лошо изпълнена подпрограма за всеки ред; iTVF не го прави. Цитирайки промяната в кариерата на Пол Уайт статия за APPLY Павел пише:

С други думи, iTVF дава възможност на оптимизатора да оптимизира заявката по начини, които просто не са възможни, когато целият този друг код трябва да бъде изпълнен. Един от многото други примери защо iTVF са по-добри е, че те са единственият от трите гореспоменати типа функции, които позволяват паралелизъм. Нека изпълним всяка функция още веднъж, този път с включен действителен план за изпълнение и с traceflag 8649 (който принуждава паралелен план за изпълнение):

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

Планове за изпълнение:

Тези стрелки, които виждате за плана за изпълнение на iTVF, са паралелизъм - всичките ви CPU (или толкова, колкото MAXDOP на вашия SQL екземпляр настройките позволяват) работят заедно. T-SQL скаларните и mtvf UDF не могат да направят това. Когато Microsoft въведе вградени скаларни UDF, тогава бих предложил тези за това, което правите, но дотогава:ако производителността е това, което търсите, тогава вградените са единственият начин и за това iTVF са единствената игра в града.

Имайте предвид, че непрекъснато наблягах на T-SQL когато говорим за функции... CLR Scalar и таблично стойностните функции могат да бъдат добре, но това е друга тема.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Добавете колона към SQL Server

  2. Изследване на опциите за изчакване на заключване с нисък приоритет в SQL Server 2014 CTP1

  3. Промяна на sql-сървър DB от табличен към многоизмерен

  4. Комбинирайте PowerShell и SQL Diagnostic Manager, за да автоматизирате наблюдението на SQL Server

  5. Как да ускорите груповото вмъкване в MS SQL Server с помощта на pyodbc