Функция с табличными значениями убивает производительность моего запроса - PullRequest
22 голосов
/ 05 ноября 2010

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

У меня такой вопрос: почему возвращение таблицы (TableVariable) вместо просто набора Select / Result может вызвать такое большое изменение в плане?

Тупик ....

Ответы [ 5 ]

57 голосов
/ 05 ноября 2010

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

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

Есть отличная ссылка на MSDN от инженеров CSS SQL Server, включая (цитата):

Но если вы используете TVF с несколькими утверждениями, это рассматривается как просто другой Таблица. Потому что нет статистика доступна, SQL Server имеет сделать некоторые предположения и в Вообще, дают низкую оценку. Если твой TVF возвращает только несколько строк, это будет быть в порядке. Но если вы собираетесь заполнить TVF тысячами ряды и если этот TVF соединяется с другие таблицы, неэффективный план может результат низкой оценки мощности.

5 голосов
/ 05 ноября 2010

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

Inline UDF с табличным значением, otoh, обрабатывается и компилируется вместе с SQL, в котором он используется, и поэтому становится part плана кэша и обрабатывается и компилируется только один раз , независимо от того, сколько строк вы генерируете.

3 голосов
/ 05 ноября 2010

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

Переменные таблицы не могут быть оптимизированы механизмом - механизм всегда «предполагает», что переменная таблицы содержит только одну строку, когда генерирует план выполнения. Это одна из причин, почему вы можете видеть странное представление.

2 голосов
/ 12 января 2017

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

Это улучшило наше время выполнения с 2 минут до 4 секунд.

Вот пример, который работал для нашей команды:

- ЗАМЕДЛЕННЫЙ ЗАПРОС (2 мин):

DECLARE @id INT = 1;

SELECT * 
FROM [data].[someTable] T
INNER JOIN [data].[tableValueFunction](@id) TVF ON TVF.id = T.id;

- БЫСТРЫЙ ЗАПРОС (4 сек.):

DECLARE @id INT = 1;

SELECT * 
INTO #tableValueFunction
FROM [data].[tableValueFunction](@id) TVF

SELECT * 
FROM [data].[someTable] T
INNER JOIN #tableValueFunction TVF ON TVF.id = T.id;
0 голосов
/ 11 июня 2015

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

  • Предложение WHERE вызывающего запроса может быть интерполировано непосредственно во встроенную табличную UDF, но не в UDF с несколькими операторами. Таким образом, если ваша табличная UDF генерирует много строк, которые будут отфильтрованы предложением WHERE вызывающего запроса, оптимизатор запросов может применить выражение WHERE непосредственно к встроенной табличной UDF, но не в UDF с несколькими утверждениями.
  • Встроенная табличная UDF ведет себя как параметризованная VIEW, если бы SQL Server имел такую ​​концепцию, тогда как табличная UDF с несколькими операторами работала бы так, как вы заполнили, а затем использовала переменную таблицы в вашем запросе. *

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

...