Это хорошо известная проблема со скалярными UDF в SQL Server.
Они не привязаны к плану, и их наложение увеличивает накладные расходы по сравнению с тем же логическим встроенным.
Следующая занимает менее 2 секунд на моей машине
WITH T10(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
) --10 rows
, T(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM T10 a, T10 b, T10 c, T10 d, T10 e, T10 f, T10 g) -- 10 million rows
SELECT MAX(N - N)
FROM T
OPTION (MAXDOP 1)
Создание простой скалярную UDF
CREATE FUNCTION dbo.F1 (@N BIGINT)
RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN (@N - @N)
END
И изменяя запрос MAX(dbo.F1(N))
вместо MAX(N - N)
она занимает около 26 секунд с STATISTICS TIME OFF
и 37 с ним.
Среднее увеличение 2,6 мкс/3,7 мкс для каждого из 10 миллионов вызовов функций.
Запуск профилировщика Visual Studio показывает, что подавляющее большинство времени взято под UDFInvoke
. Имена методов в стеке вызовов дают некоторое представление о том, что делают дополнительные накладные расходы (копирование параметров, выполнение операторов, настройка контекста безопасности).
Перемещение логики в инлайн таблицы значной функции
CREATE FUNCTION dbo.F2 (@N BIGINT)
RETURNS TABLE
RETURN(SELECT @N - @N AS X)
И переписав запрос как
SELECT MAX(X)
FROM Nums
CROSS APPLY dbo.F2(N)
выполняет так быстро, как время, как первоначальный запрос, который делает не использовать какие-либо функции.
Что вы подразумеваете под «пользовательской функцией»? Вы имеете в виду функции, созданные с помощью 'CREATE FUNCTION', или функции, написанные на C или C++ и включаемые динамически или во время компиляции? –
Многие встроенные функции реализованы как специальные операторы в плане запроса (например, стандартные агрегаторы или функции окна) или достаточно просты, что в любом случае они не будут медленными. – siride