Сегодня у меня было слишком много встреч, но я думаю, что у меня все еще есть моя мозговая программа. В мои усилия, чтобы улучшить производительность некоторых запросов я наткнулся на следующую тайну (имена таблиц и полей перефразировать):Ошибка выполнения запросов 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: этот запрос вызывается внутри другой функции, поэтому помещать результаты во временную таблицу, а затем дополнительно фильтровать их также не может быть и речи.
Я думаю, что большинство людей уже знают, что оптимизатор перемещает вещи странным образом. НО иногда запросы написаны таким образом, чтобы программист отвечал за происходящее. Если я сделаю два SELECT DISCTINCT, тогда ПРИСОЕДИНИТЕСЬ к ним, я точно уверен, что произойдет. Во всяком случае, сегодня я собираюсь опробовать некоторые вызовы, в которых внутренний запрос действительно приводит данные или где я заменяю fn_B() фиктивной функцией, просто чтобы увидеть, как изменяется поведение. –
Вы бы так подумали, но есть много исключений. Оптимизатор не идеален. Вы читали сообщение в блоге Хьюго и сообщение об ошибке только сегодня? http://sqlblog.com/blogs/hugo_kornelis/archive/2012/05/04/the-cory-case-of-the-optimizer-that-doesn-t.aspx Я видел несколько случаев, когда единственный способ чтобы поведение, которое я ожидал от оптимизатора, заключалось в том, чтобы разбить запрос на отдельные запросы. Это просто идея, которую вы можете попробовать, как я предложил выше. –
Дополнительная информация. Я запускаю внутренний запрос с параметрами, которые возвращают 6 строк. Нулевое время. Добавьте ГДЕ ==> 30 сек. Я сделал 6 явных вызовов для fn (B) с этими идентификаторами, всего 0 секунд. Я положил все это в профилировщик, и вот что дает ... SQL Server начинает лавину табличных сканирований в SAME 5 таблицах снова и снова (около 100 000 записей в журнале профилировщика), а затем выполняет запрос. Все эти таблицы отображаются внутри fn_B(), которые никогда не вызываются в исходном примере. Удаление NOLOCK не отличалось. Поэтому я начинаю понимать, что здесь что-то запутывает SQL-сервер. –