Определяемая пользователем функция Best Practice - PullRequest
4 голосов
/ 15 сентября 2010

Я предполагаю использовать некоторые пользовательские вызовы функций в некоторых моих запросах вместо использования ряда встроенных операторов case. Встроенные операторы, вероятно, будут работать лучше, но благодаря функциям их будет намного проще просматривать и, возможно, обслуживать.

Я просто хотел получить представление о том, какова типичная лучшая практика для UDF? Я понимаю, что использование их в критериях (где оговорка) может оказать существенное влияние на производительность.

Особенно в тех случаях, когда вы можете иметь много операторов when в блоке case или даже вложенных операторов case.

Спасибо

S

Ответы [ 3 ]

9 голосов
/ 15 сентября 2010

Мой постоянный ответ:

Существует распространенное заблуждение, что UDF оказывают негативное влияние на производительность.Как общее утверждение, это просто неправда.На самом деле встроенные UDF с табличными значениями на самом деле являются макросами - оптимизатор очень хорошо способен переписывать запросы, включая их, и оптимизировать их.Тем не менее, скалярные UDF обычно очень медленные.Я приведу короткий пример.

Предпосылки

Вот скрипт для создания и заполнения таблиц:

CREATE TABLE States(Code CHAR(2), [Name] VARCHAR(40), CONSTRAINT PK_States PRIMARY KEY(Code))

GO

INSERT States(Code, [Name]) VALUES('IL', 'Illinois')

INSERT States(Code, [Name]) VALUES('WI', 'Wisconsin')

INSERT States(Code, [Name]) VALUES('IA', 'Iowa')

INSERT States(Code, [Name]) VALUES('IN', 'Indiana')

INSERT States(Code, [Name]) VALUES('MI', 'Michigan')

GO

CREATE TABLE Observations(ID INT NOT NULL, StateCode CHAR(2), CONSTRAINT PK_Observations PRIMARY KEY(ID))

GO

SET NOCOUNT ON

DECLARE @i INT

SET @i=0

WHILE @i<100000 BEGIN

  SET @i = @i + 1

  INSERT Observations(ID, StateCode)

  SELECT @i, CASE WHEN @i % 5 = 0 THEN 'IL'

    WHEN @i % 5 = 1 THEN 'IA'

    WHEN @i % 5 = 2 THEN 'WI'

    WHEN @i % 5 = 3 THEN 'IA'

    WHEN @i % 5 = 4 THEN 'MI'

    END

END

GO

Когдазапрос, включающий UDF, переписывается как внешнее соединение.

Рассмотрим следующий запрос:

SELECT o.ID, s.[name] AS StateName

  INTO dbo.ObservationsWithStateNames_Join

  FROM dbo.Observations o LEFT OUTER JOIN dbo.States s ON o.StateCode = s.Code

/*

SQL Server parse and compile time:

   CPU time = 0 ms, elapsed time = 1 ms.

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'States'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.



SQL Server Execution Times:

   CPU time = 187 ms,  elapsed time = 188 ms.

*/

и сравните его с запросом, включающим встроенную табличную UDF со значением:

CREATE FUNCTION dbo.GetStateName_Inline(@StateCode CHAR(2))

RETURNS TABLE

AS

RETURN(SELECT [Name] FROM dbo.States WHERE Code = @StateCode);

GO

SELECT ID, (SELECT [name] FROM dbo.GetStateName_Inline(StateCode)) AS StateName

  INTO dbo.ObservationsWithStateNames_Inline

  FROM dbo.Observations

И план выполнения, и затраты на выполнение одинаковы - оптимизатор переписал его как внешнее соединение.Не стоит недооценивать силу оптимизатора!

Запрос, включающий скалярный UDF, выполняется намного медленнее.

Вот скалярный UDF:

CREATE FUNCTION dbo.GetStateName(@StateCode CHAR(2))

RETURNS VARCHAR(40)

AS

BEGIN

  DECLARE @ret VARCHAR(40)

  SET @ret = (SELECT [Name] FROM dbo.States WHERE Code = @StateCode)

  RETURN @ret

END

GO

Очевидно, что запрос с использованием этой UDF дает те же результаты, но у него другой план выполнения, и он значительно медленнее:

/*

SQL Server parse and compile time:

   CPU time = 0 ms, elapsed time = 3 ms.

Table 'Worktable'. Scan count 1, logical reads 202930, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.



SQL Server Execution Times:

   CPU time = 11890 ms,  elapsed time = 38585 ms.

*/

Как вы уже видели, оптимизатор может переписывать и оптимизировать запросы, включающие встроенную таблицуценные UDF.С другой стороны, запросы, включающие скалярные UDF, не переписываются оптимизатором - выполнение последнего запроса включает один вызов функции на строку, что очень медленно.Скопировано с здесь

6 голосов
/ 15 сентября 2010

Не используйте функции исключительно ради эстетики.Это можно сделать с помощью согласованного форматирования кода.

В описываемой вами ситуации вы создаете внешнюю зависимость - функция должна существовать и быть видимой для пользователя для запуска запроса.Пока SQL Server не поддерживает нечто идентичное пакетам Oracle (сборки не являются собственными SQL) ...

Существует также риск попасть в ловушку, полагая, что функции SQL выполняются как методы / функции в процедурном / OO-программировании,что они не делают, поэтому запросы могут выполнять хуже с функцией, а не без.

1 голос
/ 15 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...