Странная проблема с запросом к SQL Server - PullRequest
2 голосов
/ 22 апреля 2009

Итак, у меня странная проблема с хранимой процедурой SQL Server. В основном у меня эта долгая и сложная процедура. Примерно так:

SELECT Table1.col1, Table2.col2, col3
FROM Table1 INNER JOIN Table2
     Table2 INNER JOIN Table3
     -----------------------
     -----------------------
     (Lots more joins)
WHERE Table1.Col1 = dbo.fnGetSomeID() AND (More checks)
     -----------------------
     -----------------------
(6-7 More queries like this with the same check)

Проблема заключается в том, что проверка в предложении WHERE в конце Table1.Col1 = dbo.fnGetSomeID () . Функция dbo.fnGetSomeID () возвращает простое целочисленное значение 1 . Поэтому, когда я жестко кодирую значение 1 , где вызов функции должен быть SP, требуется всего около 15 секунд. НО, когда я заменяю его тем вызовом функции в предложении WHERE, это занимает около 3,5 минут.

Итак, я делаю это:

DECLARE @SomeValue INT
SET @SomeValue = dbo.fnGetSomeID()
--Where clause changed
WHERE Table1.Col1 = @SomeValue

Так что теперь функция вызывается только один раз. Но все те же 3,5 минуты. Итак, я продолжаю и делаю это:

DECLARE @SomeValue INT
--Removed the function, replaced it with 1
SET @SomeValue = 1
--Where clause changed
WHERE Table1.Col1 = @SomeValue

И все же это занимает 3,5 минуты. Почему производительность влияет? И как заставить его уйти?

Ответы [ 6 ]

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

Даже с @SomeValue, установленным в 1, когда у вас есть

WHERE Table1.Col1 = @SomeValue

SQL Server, вероятно, все еще рассматривает @SomeValue как переменную, а не как жестко закодированную 1, и это соответствующим образом повлияет на план запроса. А поскольку Table1 связан с Table2, а Table2 связан с Table3 и т. Д., Количество времени для выполнения запроса увеличивается. С другой стороны, когда у вас есть

WHERE Table1.Col1 = 1

План запроса блокируется с помощью Table1.Col1 с постоянным значением 1. Только потому, что мы видим

WHERE Table1.Col1 = @SomeValue

как «жесткое кодирование» не означает, что SQL видит это так же. Каждый возможный декартовой продукт является кандидатом, и @SomeValue должен оцениваться для каждого. Таким образом, применяются стандартные рекомендации - проверьте ваш план выполнения, перепишите запрос при необходимости.

Кроме того, эти столбцы объединения проиндексированы?

1 голос
/ 22 апреля 2009

Как уже упоминалось, будут различия в плане выполнения, в зависимости от того, какой подход вы выберете. Я бы посмотрел оба плана выполнения, чтобы увидеть, есть ли там очевидный ответ.

В этом вопросе описана похожая проблема, и в этом случае ответ оказался связан с настройками соединения.

Я также столкнулся почти с точно той же проблемой , что и я сам, и в этом случае я обнаружил, что использование более новых конструкций (аналитические функции в SQL 2008), видимо, сбивал с толку оптимизатор. Это может быть не так, поскольку вы используете SQL 2005, но в зависимости от остальной части вашего запроса может происходить нечто подобное.

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

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

0 голосов
/ 12 ноября 2010
 (Lots more joins)

ГДЕ Table1.Col1 = dbo.fnGetSomeID () И (дополнительные проверки)

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

Я думаю, что оптимизатор запросов ведет себя хаотично, потому что в запросе много таблиц. Когда вы говорите, что нужно искать 1, он смотрит на статистику индекса и делает хороший выбор. Когда вы говорите ему что-нибудь еще, он предполагает, что он должен присоединиться, основываясь на том, что он делает знаете, не доверяя вашей функции / переменной, чтобы вернуть выборочное значение. Для этого значение Table1.Col1 должно иметь неравномерное распределение значений. Или оптимизатор запросов не является оптимальным.

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

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

Имейте в виду, однако, что любое решение, которое вы получите, будет хрупким, зависит от данных и версии.

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

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

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

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

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

create function fnGetSomeID()
with schema_binding
returns int
... etc.
0 голосов
/ 25 апреля 2009

Вы можете попробовать подсказку ОПТИМИЗАЦИЯ ДЛЯ, чтобы форсировать план для данной константы, но это может привести к противоречивым результатам; в 2008 году вы можете использовать OPTIMIZE FOR UNKNOWN

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

Еще одна вещь, чтобы попробовать. Вместо загрузки идентификатора в переменную загрузите его в таблицу

if object_id('myTable') is not null drop myTable
select dbo.fnGetSomeID() as myID into myTable

и затем используйте

WHERE Table1.Col1 = (select myID from myTable)

в вашем запросе.

...