План на основе набора работает медленнее, чем скалярная функция со многими условиями - PullRequest
0 голосов
/ 11 июня 2018

Этот вопрос скорее гипотетический, чем реальная проблема кода.Но я представил упрощенную версию кода, чтобы проиллюстрировать этот вопрос.Пожалуйста, никаких комментариев о глупости самого кода.Фактический код слишком сложен (и запатентован), так что это лучший способ продолжить.

У меня есть скалярная функция следующим образом.

CREATE FUNCTION [dbo].[Compute_value]
(
  @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 = DATEDIFF(GETDATE(),'1/1/2000')     
    RETURN @alpha * POWER((100 / @delta), (-2 * POWER(@charle * @bravo, @x/365)))
END

Я слышал, что табличные функции обычно работают намного быстрее, чем скалярные, потому что они не RBAR.Поэтому я преобразовал логику для использования конструкции #temp_table просто для ее сравнения.Вместо примерно дюжины операторов IF у меня было бы такое же количество ОБНОВЛЕНИЙ, что и у #temp_table, и он выполнялся вдвое медленнее, чем скалярный UDF.

Я подумал, что, возможно, это происходило, потому что UDF мог быстро возвращаться при первых нескольких условиях, что приводило к тому, что большая часть скалярного UDF не выполнялась, но это не так.Изучение планов выполнения запросов для решения #temp_table, похоже, указывает на то, что обновления вызывают большую часть стоимости плана.

Чего мне здесь не хватает?Если я преобразую это в функцию табличного значения, я застрял, делая обновления всей табличной переменной для каждого условного оператора?Есть ли способ избежать этого, который, кажется, значительно замедляет ход событий?Я что-то упускаю здесь очевидное?

1 Ответ

0 голосов
/ 12 июня 2018

Здесь используется ключевое слово INLINE ТАБЛИЦА ЗНАЧЕННЫХ ФУНКЦИЙ .У вас есть два типа табличных функций T-SQL: множественные операторы и встроенные.Если ваша функция T-SQL начинается с оператора BEGIN, то это будет дерьмо - скалярное или иное.Вы не можете получить временную таблицу в табличную функцию inline , поэтому я предполагаю, что вы перешли от скалярной к табличной функции с множественными утверждениями, что, вероятно, будет хуже.

Ваша встроенная табличная функция (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.Дело не в том, что табличные функции работают быстрее, чем скалярные, а в том, что реализация Microsoft встроенных функций T-SQL быстрее, чем реализация функций 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.Чтобы понять, почему давайте посмотрим на оценочные планы выполнения:

enter image description here

Вы не видите этого, когда смотрите на фактический план выполнения, но сскаляр udf и mtvf, оптимизатор вызывает некоторые плохо выполненные подпрограммы для каждой строки;iTVF нет.Цитируя статью Пола Уайта о смене карьеры о APPLY Пол пишет:

Возможно, вам будет полезно подумать о iTVF как о представлении, которое принимает параметры.Так же, как и для представлений, SQL Server расширяет определение iTVF непосредственно в план запроса вложенного запроса, прежде чем будет выполнена оптимизация.

В результате SQL Server может применять весь спектр оптимизаций,рассматривая запрос в целом.Это как если бы вы написали расширенный запрос вручную ....

Другими словами, 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);

Планы выполнения:

enter image description here

Эти стрелки, которые вы видите для плана выполнения iTVF, являются параллелизмом - все ваши процессоры (или столько, сколько позволяют настройки MAXDOP вашего экземпляра SQL) работают вместе.Скалярные T-SQL и пользовательские функции mtvf не могут этого сделать.Когда Microsoft вводит встроенные скалярные UDF, я бы предложил их для того, что вы делаете, но до тех пор: если вам нужна производительность, тогда inline - это единственный путь, и для этого iTVF - единственная игра.в городе.

Обратите внимание, что я постоянно подчеркивал T-SQL , когда говорил о функциях ... Скалярные и табличные функции CLR могут быть просто хорошими, но это другая тема.

...