2012-05-03 3 views
1

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

SELECT X.ADId FROM 
(
    SELECT DISTINCT A.ADId 
    FROM P WITH (NOLOCK) 
    INNER JOIN A WITH (NOLOCK) ON (P.ID = A.PId) 
    INNER JOIN dbo.fn_A(16) AS VD ON (VD.DId = A.ADId) 
    LEFT JOIN DPR ON (LDID = A.ADId) 
    WHERE ((A.ADId = 1) OR ((HDId IS NOT NULL) AND (HDId = 1))) AND 
      (P.PS NOT IN(5,7)) AND (A.ASP IN (2, 3)) 
) X 
WHERE (dbo.fn_B(X.ADId, 16) = 1) 

Как вы увидите, содержимое внутреннего запроса, в основном не имеет значения. Весь смысл в том, что я хотел избежать того, чтобы fn_B() вызывал каждую запись, потому что они содержали повторяющиеся значения для ADId, поэтому я сделал SELECT DISTINCT внутри, а затем отфильтровал отдельные записи. Звучит разумно?

Здесь начинается тайна ...

Внутренний запрос не возвращает никаких записей (для указанных параметров). Если я прокомментирую «WHERE fn_B() = 1», то запрос запускается в нулевое время (и не возвращает никаких результатов). Если я верну его обратно, запрос займет 6-10 секунд, снова не получив никаких результатов.

Это, кажется, превзошло здравый смысл или, по крайней мере, мой общий смысл SQL :-) Если внутренний запрос не возвращает данные, внешние условия никогда не должны оцениваться правильно?

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

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

Другие варианты, такие как использование старого запроса (который может вызывать fn_B() несколько раз с одинаковым значением), имеют такое же поведение. Если я удалю условие, я не получу никаких записей за нулевое время. Если я верну его, то никаких записей за 10 секунд не будет.

Любые идеи кто-нибудь?

Спасибо за ваше время :-)

PS1: Я попытался воспроизвести ситуацию на данном TempDb с помощью простого запроса, но я не мог сделать это. Это происходит только на моих реальных таблицах. PS2: этот запрос вызывается внутри другой функции, поэтому помещать результаты во временную таблицу, а затем дополнительно фильтровать их также не может быть и речи.

ответ

0

Как примечание, оптимизатор не читает запрос так же, как и вы. Даже если вы считаете, что определенный порядок должен иметь место или что короткое замыкание может иметь наибольший смысл, оптимизатор все еще может оценивать CTE/подзапросы в том порядке, который вы не ожидаете. Обходной путь, который вы можете попробовать, - это выбор первого запроса в таблице #temp, а затем запуск фильтра функций в таблице #temp. Это должно заставить порядок оценки, даже если он совершенно неинтуитивный и менее элегантный.

EDIT

Кроме того, в то время как он может выполнять медленнее, мне интересно, что произойдет, если вы запустите запрос без NOLOCK, или в RCSI вместо. Различные блокирующие семантики могут отключать оптимизатор.

+0

Я думаю, что большинство людей уже знают, что оптимизатор перемещает вещи странным образом. НО иногда запросы написаны таким образом, чтобы программист отвечал за происходящее. Если я сделаю два SELECT DISCTINCT, тогда ПРИСОЕДИНИТЕСЬ к ним, я точно уверен, что произойдет. Во всяком случае, сегодня я собираюсь опробовать некоторые вызовы, в которых внутренний запрос действительно приводит данные или где я заменяю fn_B() фиктивной функцией, просто чтобы увидеть, как изменяется поведение. –

+0

Вы бы так подумали, но есть много исключений. Оптимизатор не идеален. Вы читали сообщение в блоге Хьюго и сообщение об ошибке только сегодня? http://sqlblog.com/blogs/hugo_kornelis/archive/2012/05/04/the-cory-case-of-the-optimizer-that-doesn-t.aspx Я видел несколько случаев, когда единственный способ чтобы поведение, которое я ожидал от оптимизатора, заключалось в том, чтобы разбить запрос на отдельные запросы. Это просто идея, которую вы можете попробовать, как я предложил выше. –

+0

Дополнительная информация. Я запускаю внутренний запрос с параметрами, которые возвращают 6 строк. Нулевое время. Добавьте ГДЕ ==> 30 сек. Я сделал 6 явных вызовов для fn (B) с этими идентификаторами, всего 0 секунд. Я положил все это в профилировщик, и вот что дает ... SQL Server начинает лавину табличных сканирований в SAME 5 таблицах снова и снова (около 100 000 записей в журнале профилировщика), а затем выполняет запрос. Все эти таблицы отображаются внутри fn_B(), которые никогда не вызываются в исходном примере. Удаление NOLOCK не отличалось. Поэтому я начинаю понимать, что здесь что-то запутывает SQL-сервер. –

0

Мы отправили эту проблему в поддержку Microsoft для SQL Server R2 (я должен прокомментировать их удивительное время отклика и общие процедуры обслуживания). Мы дали им копию нашей БД, которая воспроизводит эту проблему, и наш обходной путь, они воспроизводили его сами и через пару дней здесь ответ мы получили ответ:

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

Это довольно дипломатический способ сказать: «Да, оптимизатор разбирает вещи по вашему запросу, поэтому, пожалуйста, используйте обходной путь». Если вы хотите назвать это ошибкой, назовите ее ошибкой, это не имеет значения.

Только для записи обходной путь состоял в том, чтобы поместить вызов fn_B() в список SELECT запроса на один уровень выше SELECT DISTINCT, а затем отфильтровать его результат в условии WHERE. Странно, но это трюк.