Чи дозволяється SQL Server оцінювати A <> B
як A < B OR A > B
, навіть якщо один із виразів не є детермінованим?
Це дещо суперечливий момент, і відповідь - кваліфіковане "так".
Найкраща дискусія, про яку я знаю, була надана у відповідь на повідомлення про помилку підключення Іціка Бен-Гана Про помилку NEWID та Table Express , яке було закрито, оскільки це не виправлено. З тих пір Connect не вийшов, тому посилання є на веб-архів. На жаль, через користь Коннету було втрачено (або зробити його складніше) багато корисного матеріалу. Як би там не було, найкорисніші цитати від Jim Hogg з Microsoft є:
Це підходить до самої суті проблеми - чи дозволяється оптимізація змінити семантику програми? Тобто, якщо програма дає певні відповіді, але працює повільно, чи правомірним є те, що Оптимізатор запитів змусить цю програму працювати швидше, але також змінити отримані результати?
Перш ніж кричати "НІ!" (теж моя особиста схильність :-), врахуйте: хороша новина полягає в тому, що в 99% випадків відповіді однакові. Тож оптимізація запитів - це явна перемога. Погана новина полягає в тому, що якщо запит містить побічний код, то різні плани МОЖЧЕ дають різні результати. І NEWID () - одна з таких побічних (недетермінованих) 'функцій', яка піддає різниці. [Насправді, якщо ви експериментуєте, ви можете розробити інші - наприклад, коротке замикання клавіш AND: змусити другий додаток кидати арифметичний поділ на нуль - різні оптимізації можуть виконати цей другий пункт до першого пункту] Це відображає Пояснення Крейга, в іншому місці цього потоку, що SqlServer не гарантує, коли виконуються скалярні оператори.
Отже, у нас є вибір: якщо ми хочемо гарантувати певну поведінку за наявності недетермінованого (побічного) коду - щоб результати JOIN, наприклад, дотримувались семантики виконання вкладеного циклу - тоді ми може використовувати відповідні ВАРІАНТИ, щоб змусити цю поведінку - як зазначає UC. Але отриманий код буде працювати повільно - це, по суті, вартість збивання оптимізатора запитів.
Все, що було сказано, ми рухаємо Оптимізатор запитів у напрямку поведінки «як очікувалося» для NEWID () - торгуючи продуктивністю для «результатів, як очікувалося».
Одним із прикладів зміни поведінки в цьому плані з часом NULLIF працює неправильно з недетермінованими функціями, такими як RAND () . Існують також інші подібні випадки, в яких використовується, наприклад, COALESCE
підзапит, який може призвести до несподіваних результатів, і які також вирішуються поступово.
Джим продовжує:
Замикання петлі. . . Я обговорював це питання з командою Dev. І врешті-решт ми вирішили не змінювати поточну поведінку з наступних причин:
1) Оптимізатор не гарантує терміни чи кількість виконання скалярних функцій. Це довготривалий принцип. Це фундаментальне "вільне середовище", яке дозволяє оптимізатору достатньо свободи отримати значні покращення у виконанні плану запитів.
2) Ця "поведінка один раз на рядок" не є новою проблемою, хоча вона широко не обговорюється. Ми почали налаштовувати його поведінку ще у випуску Yukon. Але точно важко точно визначити, у всіх випадках, що саме це означає! Наприклад, чи застосовується це до проміжних рядків, обчислених "на шляху" до кінцевого результату? - у такому випадку це чітко залежить від обраного плану. Або це стосується лише рядків, які з часом з’являться у завершеному результаті? - тут відбувається жахлива рекурсія, адже я впевнений, що ти погодишся!
3) Як я вже згадував раніше, ми за замовчуванням «оптимізуємо продуктивність» - це добре для 99% випадків. 1% випадків, коли це може змінити результати, досить легко помітити побічні «функції», такі як NEWID, і їх легко «виправити» (як наслідок торгівлі перф.). Цей за замовчуванням для "оптимізації продуктивності" знову є давно встановленим та прийнятим. (Так, це не позиція, яку обирають компілятори для звичайних мов програмування, але так і нехай).
Отже, наші рекомендації:
a) Уникайте покладатися на негарантовану семантику часу та кількості виконань. b) Уникайте використання NEWID () глибоких табличних виразів. в) Використовуйте ОПЦІЮ, щоб змусити певну поведінку (торгувати perf)
Сподіваємось, що це пояснення допомагає з’ясувати наші причини закриття цієї помилки як «не виправлено».
Цікаво, що AND NOT (s_guid = NEWID())
дає той самий план виконання
Це наслідок нормалізації, що відбувається дуже рано під час складання запитів. Обидва вирази складаються в точно однаковій нормованій формі, тому створюється однаковий план виконання.