2010-09-15 5 views
4

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

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

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

Спасибо,

S

+0

Лучшей практикой было бы не использовать функции по соображениям безопасности. – Woot4Moo

ответ

9

Мой консервированный ответ:

Существует распространенное заблуждение, что UDFs имеют негативное влияние на производительность. Как общее утверждение, это просто неверно. Фактически, встроенные табличные значения 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, не переписываются оптимизатором - выполнение последнего запроса включает в себя один вызов функции в строке, который очень медленный. Код статьи: here Факс:

+0

Отличный ответ, pasib) – 2011-07-01 09:32:40

+0

Вы никогда не должны запрашивать таблицы в скалярной функции. Период. SQL Server не оптимизирует их вообще. Они даже исключены из планов запросов. Вы буквально пишете запрос N + 1 в SQL. N + 1 запросов - это дьявол и никогда не будет работать со временем. – BradLaney

6

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

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

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

+0

Спасибо OMG Я ценю обратную связь. «недавно переключиться с Oracle» – scarpacci

1

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

+0

это справедливо только для скалярных UDF и совершенно неверно для встроенных. –