Можно ли оценивать SQL Server A <> B
как A < B OR A > B
, даже если одно из выражений недетерминировано?
Это несколько спорный момент, и ответом является квалифицированное «да».
Лучшее обсуждение, о котором я знаю, было дано в ответе на сообщение об ошибке Connect Ицик Бен-Гана «Ошибка с NEWID» и «Выражения таблиц» , которое было закрыто, так как не будет исправлено. С тех пор Connect был удален, поэтому есть ссылка на веб-архив. К сожалению, много полезного материала было потеряно (или стало труднее найти) после кончины Connect. Во всяком случае, самые полезные цитаты из Джима Хогга из Microsoft:
Это затрагивает самую суть проблемы - позволяет ли оптимизация изменять семантику программы? То есть: если программа дает определенные ответы, но работает медленно, то является ли законным для оптимизатора запросов заставить эту программу работать быстрее, но также изменить полученные результаты?
Прежде чем кричать "НЕТ!" (мой личный уклон тоже :-), учтите: хорошая новость в том, что в 99% случаев ответы одинаковы. Таким образом, оптимизация запросов - явная победа. Плохая новость заключается в том, что если запрос содержит код с побочными эффектами, тогда разные планы МОГУТ действительно давать разные результаты. И NEWID () - одна из таких побочных (недетерминированных) «функций», которая выявляет разницу. [На самом деле, если вы экспериментируете, вы можете придумать другие - например, оценку короткого замыкания предложений AND: заставьте второе предложение выбросить арифметическое деление на ноль - различные оптимизации могут выполнить это второе предложение ДО первого предложения] Это отражает Объяснение Крейга, в другом месте в этой теме, что SqlServer не гарантирует, что скалярные операторы выполняются.
Таким образом, у нас есть выбор: если мы хотим гарантировать определенное поведение при наличии недетерминированного (побочного) кода - так, чтобы результаты JOIN, например, следовали семантике выполнения вложенного цикла - тогда мы может использовать соответствующие ОПЦИИ для форсирования такого поведения - как отмечает UC. Но полученный код будет работать медленно - это, по сути, стоимость работы с оптимизатором запросов.
Все это говорит о том, что мы перемещаем Оптимизатор запросов в направлении «как ожидается» поведения для NEWID () - компенсируя производительность для «результатов как ожидалось».
Одним из примеров изменения поведения в этом отношении во времени является то, что NULLIF некорректно работает с недетерминированными функциями, такими как RAND () . Существуют также другие подобные случаи, использующие, например, COALESCE
подзапрос, который может привести к неожиданным результатам и который также решается постепенно.
Джим продолжает:
Закрытие петли. , , Я обсуждал этот вопрос с командой разработчиков. И в итоге мы решили не менять текущее поведение по следующим причинам:
1) Оптимизатор не гарантирует время или количество выполнений скалярных функций. Это давно установленный принцип. Это фундаментальная «свобода действий», которая дает оптимизатору достаточно свободы, чтобы добиться значительных улучшений в выполнении плана запросов.
2) Это «поведение по одному разу в строке» не является новой проблемой, хотя и широко не обсуждается. Мы начали настраивать его поведение еще в выпуске Yukon. Но довольно сложно точно определить, во всех случаях, что именно это означает! Например, применимо ли это к промежуточным строкам, рассчитанным «по пути» к конечному результату? - в этом случае это явно зависит от выбранного плана. Или это относится только к строкам, которые в конечном итоге появятся в готовом результате? - здесь идет отвратительная рекурсия, я уверен, вы согласитесь!
3) Как я упоминал ранее, мы по умолчанию «оптимизируем производительность», что хорошо в 99% случаев. 1% случаев, когда это может изменить результаты, довольно легко обнаружить - «побочные» «функции», такие как NEWID, - и их легко «исправить» (следствие торговли). Это значение по умолчанию для «оптимизации производительности» снова, давно установлено и принято. (Да, это не позиция, выбранная компиляторами для обычных языков программирования, но так и будет).
Итак, наши рекомендации:
а) Избегайте использования негарантированной синхронизации и семантики количества выполнений. б) Избегайте использования NEWID () глубоко в табличных выражениях. c) Используйте OPTION, чтобы вызвать определенное поведение (торговля перф)
Надеюсь, что это объяснение поможет прояснить причины, по которым мы можем закрыть эту ошибку, поскольку "не исправлю".
Интересно, что AND NOT (s_guid = NEWID())
дает тот же план выполнения
Это является следствием нормализации, которая происходит очень рано во время компиляции запроса. Оба выражения компилируются в одну и ту же нормализованную форму, поэтому создается один и тот же план выполнения.