Функция, связанная с таблицей, убивающая мою эффективность запросов

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

Мой вопрос в том, почему бы ему вернуть таблицу (TableVariable), а не только выбор/результат, вызывают такое большое изменение в плане?

Тупик....

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

DECLARE @id INT = 1;

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

- FAST QUERY (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;