разница в производительности между пользовательской функцией и хранимыми процедурами - PullRequest
13 голосов
/ 22 декабря 2009

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

Ответы [ 6 ]

18 голосов
/ 22 декабря 2009

Нет разницы в скорости между запросом, выполняемым внутри функции, и одним, выполняемым внутри процедуры.

Хранимые процедуры имеют проблемы с агрегацией результатов, их нельзя объединить с другими хранимыми процедурами. Онильный раствор действительно громоздок, так как включает в себя вывод процедуры в таблицу с INSERT ... EXEC ... и затем использование приведенной таблицы.

Преимущество функций состоит в том, что они легко компонуются, поскольку функцию табличных значений можно размещать в любом месте, где ожидается табличное выражение (FROM, JOIN, APPLY, IN и т. Д.) Но функции имеют некоторые очень серьезные ограничения в отношении того, что разрешено в функции, а что нет, именно потому, что они могут появляться в любом месте запроса.

Значит, это действительно яблоко для апельсинов. Решение зависит не от производительности, а от требований. Как правило, все, что возвращает набор данных, должно быть представлением или табличной функцией. Все, что манипулирует данными, должно быть процедурой.

15 голосов
/ 22 декабря 2009

Не все UDF плохо влияют на производительность.

Существует распространенное заблуждение, что 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 плохо влияют на производительность.

1 голос
/ 22 декабря 2009

Как только SQL увидит BEGIN или END, система не сможет упростить содержимое.

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

Лучше всего на самом деле использовать либо представление, либо встроенную табличную функцию, чтобы SQL мог упростить ее и выполнить только ту часть, которая вас интересует. Посмотрите мой пост "Опасности НАЧАЛА END "в моем блоге для получения дополнительной информации.

1 голос
/ 22 декабря 2009

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

Тем не менее, UDF нужны для КАЖДОГО РЯДА, поэтому я буду осторожен, когда вы их используете. Это однажды привело меня к настоящим неприятностям. Так много, что я никогда не забуду.

0 голосов
/ 25 марта 2017

Когда я впервые попытался использовать встроенную табличную функцию (TVF), на самом деле это заняло от 66 до 76% (1,147–1,2 против 0,683 с.) SP))!?! Это было в среднем 100 итераций с 89 строками на итерацию. Мой SP просто выполнял стандарт set nocount on, за которым следовал сложный (но все еще одиночный) оператор select (с 5 inner join и 2 outer join (с одним из inner join, имеющим on Выражение со встроенным select (которое само имело выражение where (со встроенным select + inner join))) и group by и order by с 5 столбцами и count ). Вызывающая сторона - это insert into временная таблица (с identity столбцом, но без ключей или индексов) - оператор. Inline TVF занимал на 66% больше времени даже без order by, который делал SP. Когда я добавил его обратно (к select, вызывающему Inline TVF, поскольку в Inline TVF не может быть order by), это заняло еще больше времени (76%)!?!

0 голосов
/ 22 декабря 2009

На простые операторы SELECT больше всего влияют индексы в таблицах, к которым вы обращаетесь.

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

При написании запросов стоит потратить время на изучение индексов, оптимизаторов, первичных ключей и так далее. Выбор нескольких баз данных; SQL Server отличается от MySQL, а Oracle отличается от них обоих. Их намного больше, и каждый в чем-то отличается.

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

Функции могут быть скалярными (возвращать один результат) или возвращать табличные данные.

Вполне возможно написать неэффективные функции и хранимые процедуры. Важно спросить себя, нужна ли вам эта функциональность и как вы будете ее поддерживать.

Если у вас еще нет книги Джо Селко, то сейчас самое время инвестировать.

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