Почему скалярные функции SQL Server работают медленнее? - PullRequest
15 голосов
/ 29 апреля 2009

Почему скалярные функции приводят к тому, что запросы выполняются кумулятивно медленнее, чем больше раз подряд они используются?

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

Я обрезал некоторые вещи, чтобы сделать этот пост короче ... но просто чтобы вы поняли, как все настроено.

CREATE TABLE [dbo].[GIS_Location](
        [ID] [int] IDENTITY(1,1) NOT NULL, --PK
        [Lat] [int] NOT NULL,
        [Lon] [int] NOT NULL,
        [Postal_Code] [varchar](7) NOT NULL,
        [State] [char](2) NOT NULL,
        [City] [varchar](30) NOT NULL,
        [Country] [char](3) NOT NULL,

CREATE TABLE [dbo].[Address_Location](
    [ID] [int] IDENTITY(1,1) NOT NULL, --PK
    [Address_Type_ID] [int] NULL,
    [Location] [varchar](100) NOT NULL,
    [State] [char](2) NOT NULL,
    [City] [varchar](30) NOT NULL,
    [Postal_Code] [varchar](10) NOT NULL,
    [Postal_Extension] [varchar](10) NULL,
    [Country_Code] [varchar](10) NULL,

Тогда у меня есть две функции, которые ищут LAT и LON.

CREATE FUNCTION [dbo].[usf_GIS_GET_LAT]
(
    @City VARCHAR(30),
    @State CHAR(2)
)
RETURNS INT 
WITH EXECUTE AS CALLER
AS
BEGIN
    DECLARE @LAT INT

    SET @LAT = (SELECT TOP 1 LAT FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)

RETURN @LAT
END


CREATE FUNCTION [dbo].[usf_GIS_GET_LON]
(
    @City VARCHAR(30),
    @State CHAR(2)
)
RETURNS INT 
WITH EXECUTE AS CALLER
AS
BEGIN
    DECLARE @LON INT

    SET @LON = (SELECT TOP 1 LON FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)

RETURN @LON
END

Когда я запускаю следующее ...

SET STATISTICS TIME ON

SELECT
    dbo.usf_GIS_GET_LAT(City,[State]) AS Lat,
    dbo.usf_GIS_GET_LON(City,[State]) AS Lon
FROM
    Address_Location WITH(NOLOCK)
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)

SET STATISTICS TIME OFF

100 ~ = 8 мс, 200 ~ = 32 мс, 400 ~ = 876 мс

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

Ответы [ 7 ]

27 голосов
/ 01 мая 2009

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

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

В качестве побочного эффекта встраивания они не могут содержать процедурный код (не объявить @variable; установить @variable = ..; return). Однако они могут возвращать несколько строк и столбцов.

Вы можете переписать свои функции примерно так:

create function usf_GIS_GET_LAT(
    @City varchar (30),
    @State char (2)
)
returns table
as return (
  select top 1 lat
  from GIS_Location with (nolock) 
  where [State] = @State
    and [City] = @City
);

GO

create function usf_GIS_GET_LON (
    @City varchar (30),
    @State char (2)
)
returns table
as return (
  select top 1 LON
  from GIS_Location with (nolock)
  where [State] = @State
    and [City] = @City
);

Синтаксис их использования также немного отличается:

select
    Lat.Lat,
    Lon.Lon
from
    Address_Location with (nolock)
    cross apply dbo.usf_GIS_GET_LAT(City,[State]) AS Lat
    cross apply dbo.usf_GIS_GET_LON(City,[State]) AS Lon
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)
6 голосов
/ 29 апреля 2009

Нет.

В скалярных функциях нет ошибки, которая вызывала бы экспоненциальное снижение производительности в зависимости от количества строк в скалярной функции. Попробуйте еще раз свои тесты и посмотрите на профилировщик SQL, взглянув на столбцы CPU и READS и DURATION. Увеличьте размер теста, чтобы он включал тесты, которые занимают более секунды, две секунды и пять секунд.

CREATE FUNCTION dbo.slow
(
    @ignore int
)
RETURNS INT 
AS
BEGIN
    DECLARE @slow INT
    SET @slow = (select count(*) from sysobjects a 
        cross join sysobjects b 
        cross join sysobjects c 
        cross join sysobjects d 
        cross join sysobjects e 
        cross join sysobjects f
    where a.id = @ignore) 

    RETURN @slow
END
go
SET STATISTICS TIME ON

select top 1 dbo.slow(id)
from sysobjects
go
select top 5 dbo.slow(id)
from sysobjects
go
select top 10 dbo.slow(id)
from sysobjects
go
select top 20 dbo.slow(id)
from sysobjects
go
select top 40 dbo.slow(id)
from sysobjects

SET STATISTICS TIME OFF

Выход

SQL Server Execution Times:
   CPU time = 203 ms,  elapsed time = 202 ms.


SQL Server Execution Times:
   CPU time = 889 ms,  elapsed time = 939 ms.

SQL Server Execution Times:
   CPU time = 1748 ms,  elapsed time = 1855 ms.

SQL Server Execution Times:
   CPU time = 3541 ms,  elapsed time = 3696 ms.


SQL Server Execution Times:
   CPU time = 7207 ms,  elapsed time = 7392 ms.

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

3 голосов
/ 19 мая 2009

Вы можете обернуть свою функциональность во встроенный TVF, это будет намного быстрее:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/05/23/reuse-your-code-with-cross-apply.aspx

2 голосов
/ 29 апреля 2009

Вы вызываете функцию два раза (два выбранных обращения к БД) для каждой строки в наборе результатов.

, чтобы ускорить ваш запрос, присоединитесь прямо к GIS_Location и пропустите функции:

SELECT
    g.Lat,
    g.Lon
FROM
    Address_Location        l WITH(NOLOCK)
    INNER JOIN GIS_Location g WITH(NOLOCK) WHERE l.State = g.State AND l.City = g.City
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)

Я не уверен, почему NOLOCK, или сумасшедшее предложение, я просто скопировал вопрос ...

0 голосов
/ 07 ноября 2018

Обычно скалярные функции намного медленнее, чем встроенные аналоги TVF. К счастью, для многих сценариев это изменится.

SQL Server 2019 представит Скалярный UDF Inlining :

Функция в наборе функций интеллектуальной обработки запросов. Эта функция повышает производительность запросов, которые вызывают скалярные пользовательские функции в SQL Server (начиная с предварительного просмотра SQL Server 2019)

Скалярные пользовательские функции T-SQL

Определяемые пользователем функции, которые реализованы в Transact-SQL и возвращают одно значение данных, называются скалярными пользовательскими функциями T-SQL. Пользовательские функции T-SQL - это элегантный способ обеспечить повторное использование кода и модульность в запросах SQL. Некоторые вычисления (например, сложные бизнес-правила) легче выразить в императивной форме UDF. Пользовательские функции помогают в построении сложной логики, не требуя опыта в написании сложных SQL-запросов.

Скалярные UDF обычно заканчиваются плохо по следующим причинам.

  • Итеративный вызов
  • Отсутствие стоимости
  • Толкованное исполнение
  • Серийное исполнение

Автоматическое наложение скалярных UDF

Цель функции встраивания Scalar UDF - повысить производительность запросов, которые вызывают скалярные UDF T-SQL, где выполнение UDF является основным узким местом.

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


Требования к встроенным скалярным UDF

Скалярный T-SQL UDF может быть встроенным, если выполнены все следующие условия верны:

  • UDF написан с использованием следующих конструкций:

    1. DECLARE, SET: объявление и присваивание переменной.
    2. SELECT: SQL-запрос с присвоением одной / нескольких переменных1.
    3. IF / ELSE: ветвление с произвольными уровнями вложенности.
    4. RETURN: один или несколько операторов возврата.
    5. UDF: вызовы вложенных / рекурсивных функций2.
    6. Прочее: реляционные операции, такие как EXISTS, ISNULL.
  • UDF не вызывает никакой встроенной функции, которая зависит от времени (например, GETDATE ()) или имеет побочные эффекты3 (например, NEWSEQUENTIALID ()).

  • UDF использует предложение EXECUTE AS CALLER (поведение по умолчанию, если не указано предложение EXECUTE AS).
  • UDF не ссылается на табличные переменные или табличные параметры.
  • Запрос, вызывающий скалярную UDF, не ссылается на скалярный вызов UDF в своем предложении GROUP BY.
  • UDF изначально не скомпилирован (взаимодействие поддерживается).
  • UDF не используется в вычисляемом столбце или определении проверочного ограничения.
  • UDF не ссылается на пользовательские типы.
  • Нет подписей, добавленных в UDF.
  • UDF не является функцией разделения.

Проверка, является ли функция встроенной:

SELECT OBJECT_NAME([object_id]) AS name, is_inlineable
FROM sys.sql_modules
WHERE [object_id] = OBJECT_ID('schema.function_name')

Включение / отключение функции на уровне базы данных:

ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON/OFF;
0 голосов
/ 29 апреля 2009

Посмотрите, работает ли это лучше ... Или, может быть, отдельное внутреннее соединение?

select a.*,
(select top 1 g.Lat from GIS_Location g where g.City = a.City and g.State = a.State) as Lat,
(select top 1 g.Lon from GIS_Location g where g.City = a.City and g.State = a.State) as Lon
from Address_Location a
where a.ID in (select top 100 ID from Address_Location order by ID desc)

Что касается производительности скалярной функции, я не уверен.

0 голосов
/ 29 апреля 2009

Проще говоря, потому что выражения SQL с пользовательскими функциями менее эффективны, чем выражения SQL без них. Логика выполнения не может быть оптимизирована; и издержки функции (включая протоколы вызова) должны возникать для каждой строки.

KMike совет хорош. ГДЕ .. IN (ВЫБРАТЬ что-то) вряд ли будет эффективным шаблоном, и в этом случае его можно легко заменить на JOIN.

...