Встроенная таблица оценивает производительность UDF - PullRequest
3 голосов
/ 03 августа 2011

Я использую SQL Server 2008R2. Я написал следующий табличный UDF, который принимает либо скалярные значения того или иного, либо обоих в качестве параметров и возвращает таблицу с идентификатором столбцов, этим и этим. Я вижу ужасную производительность, когда вызываю эту функцию изнутри сложного запроса, но не когда я вызываю ее в простых запросах. Я хотел бы знать, есть ли у кого-нибудь какие-либо идеи о том, что я делаю, что замедляет ход событий. Определение функции следующее:

CREATE function dbo.fn_getThisThat (@this nvarchar(255), @that nvarchar(255))
RETURNS TABLE
RETURN

SELECT These.this, Those.that, COALESCE(These.ID, Those.ID) as ID
FROM 
    (
    SELECT col1 as ‘this’, value1, value2, ID
    FROM (
        SELECT t1.col1, t1.col2, t1.col3, t2.col1
        FROM t1
        JOIN t2
            ON t1.col1 = t2.col1
        WHERE t2.col2 = ‘this’
        AND t1.col1 in (‘value1’, ‘value2’)
        ) SOURCE
    PIVOT (
        MAX(t1.col3) FOR t1.col1 in (value1, value2)
        ) AS pvt
    ) These
JOIN
    (
    SELECT t1.col1, t1.col2, t2.col1, t3.ID
    FROM t3
    JOIN t1
        ON t3.col1 = t1.col1
    JOIN t2
        ON t2.col1 = t1.col1
    WHERE t3.col3 = ‘value3’
    AND t1.col3 = ‘value1’
    AND t2.col3 = ‘value2’
    ) Those
WHERE that = @that
OR this = @this

Следующий оператор обрабатывается очень быстро (<1 сек) при передаче скалярных параметров: </p>

SELECT * FROM dbo.fn_getThisThat(scalarValue, null)

Или в относительно простом запросе, например:

SELECT t1.col1, t1.col2, fn.This
FROM t1
CROSS APPLY dbo.fn_getThisThat(t1.col3, null)

… но он запаздывает (от времени обработки ~ 1 секунды до ~ 2: 30 секунд) при вызове в более сложном запросе, как этот (в псевдокоде: дайте мне знать, если информации недостаточно):

DECLARE @table (a, b, c)
INSERT @table (a, b, c)
SELECT (values)

SELECT t1.c1, t1.c2, t1.c3
FROM
    (
    SELECT a.c1, COUNT(fn.That) as c2, COUNT(a.c2) as c3
    FROM a
    JOIN b ON (join terms)
    CROSS APPLY dbo.fn_getThisThat(a.c2, null) fn
    WHERE a.c1 IN (SELECT a FROM @table)
    GROUP BY a.c1
    ) t1

У кого-нибудь есть предложения относительно того, что я делаю, чтобы убить скорость во втором запросе? Я изменил функцию так, чтобы она принимала массив, а не скалярные параметры, но это исключило мою возможность перекрестного применения (в последнем фрагменте кода). Насколько я могу судить по анализатору запросов, снижение производительности связано с перекрестным применением моей функции. Я думал, что не столкнусь с RBAR, так как мой UDF не был многократным, но, возможно, я ужасно неправ ...?

EDIT: Еще одна вещь: план выполнения запроса показывает, что сама функция вносит только 2% в пакет; более крупный запрос дает 98%, но большая часть его стоимости - поиск индекса и сканирование таблицы, а не параллелизм. Это заставило меня задуматься о том, что, возможно, вызов функции связан не столько с медлительностью запроса, сколько с отсутствием индексов в некоторых из задействованных таблиц (к сожалению, у меня нет большого контроля над добавлением индексы.). Я выполнил запрос без вызова функции, и сканирование таблицы и поиск индекса по-прежнему показывают высокий уровень, но запрос завершается примерно за 8 секунд. Итак, мы вернулись к функции ...?

Ответы [ 5 ]

3 голосов
/ 03 августа 2011

Возможно, вы захотите изменить свой UDF так, чтобы он везде использовал имена таблиц из двух частей, чтобы вы могли добавить к нему предложение SCHEMABINDING.См. Улучшение планов запросов с опцией SCHEMABINDING для пользовательских функций T-SQL .

1 голос
/ 03 августа 2011

Из статьи MSDN для Apply ( MSDN - Apply ):

"Оператор APPLY позволяет вызывать табличную функцию для каждой возвращаемой строки по внешнему табличному выражению запроса. "

В вашем примере показана группа по.Можно ли вызывать вашу функцию после группировки строк, а не в этом конкретном запросе?Это сократит количество строк, для которых должна вызываться функция.

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

1 голос
/ 03 августа 2011

Как уже указывалось, CROSS APPLY вызывается для каждой строки во внешнем запросе. Итак, ключевой вопрос здесь состоит в том, сколько строк возвращается из:

DECLARE @table (a, b, c)
INSERT @table (a, b, c)
SELECT (values)

SELECT t1.c1, t1.c2, t1.c3
FROM
    (
    SELECT a.c1
    FROM a
    JOIN b ON (join terms)
    WHERE a.c1 IN (SELECT a FROM @table)
    ) t1

Это количество вызовов, которые будут сделаны на ваш TVF. Если (и это большое значение, если) TVF имеет одинаковое время выполнения для любого значения a.c2, то соответствующим сравнением производительности является единственное время выполнения вашей функции * строк, возвращаемых из запроса выше.

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

0 голосов
/ 04 августа 2011

Пока что мне удалось увеличить производительность с ~ 2:30 до ~ 0:17. Это лучше, но все еще не идеально. Я сделал следующее:

  • Добавлена ​​привязка схемы к моему тв udf (спасибо, Ремус!). Это помогло, но, похоже, оказало меньшее влияние на производительность, чем следующее.

  • Реструктурированный основной запрос для присоединения к @table, а не для ссылки на него в подзапросе: это, похоже, помогло больше всего, и именно из этого, похоже, и произошел большой прирост производительности.

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

Спасибо всем!

0 голосов
/ 03 августа 2011

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

У меня нет большого опыта использования подзапросов PIVOT в ситуациях CROSS APPLY - это кажется мне проблемой. Но план выполнения скажет вам наверняка.

...