SQL Serverで興味深い問題が発生しました。次の再現例を考えてみます。
_CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');
SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
AND s_guid <> NEWID();
DROP TABLE #test;
_
s_guid <> NEWID()
条件がまったく役に立たないように見えることを少しの間忘れてください-これは最小限の再現例です。 NEWID()
が特定の定数値と一致する確率は非常に小さいため、毎回TRUEと評価する必要があります。
しかし、そうではありません。このクエリを実行すると通常1行が返されますが、sometimes(かなり頻繁に、 10回のうち1回以上)0行を返します。私は自分のシステムのSQL Server 2008でそれを再現しましたが、上記のフィドル(SQL Server 2014)でオンラインで再現できます。
実行プランを見ると、クエリアナライザーが明らかに条件をs_guid < NEWID() OR s_guid > NEWID()
に分割していることがわかります。
...これが失敗する理由を完全に説明しています(最初に生成されたIDが指定されたIDより小さく、2番目のIDが大きい場合)。
SQL Serverは、式の1つが非決定的であっても、_A <> B
_を_A < B OR A > B
_として評価できますか?はいの場合、それはどこに文書化されていますか?それともバグを見つけましたか?
興味深いことに、AND NOT (s_guid = NEWID())
は同じ実行プラン(および同じランダムな結果)を生成します。
この問題は、開発者が特定の行をオプションで除外して使用したいときに見つかりました。
_s_guid <> ISNULL(@someParameter, NEWID())
_
「ショートカット」として:
_(@someParameter IS NULL OR s_guid <> @someParameter)
_
ドキュメントやバグの確認を探しています。コードはそれほど重要ではないため、回避策は必要ありません。
SQL Serverは、式の1つが非決定的であっても、_
A <> B
_を_A < B OR A > B
_として評価できますか?
これはやや論争の的となる点であり、答えは適格な「はい」です。
私が知っている最良の議論は、Itzik Ben-GanのConnectバグレポート NEWIDとテーブル式のバグ への回答であり、修正されなかったためクローズされました。コネクトは廃止されたため、ウェブアーカイブへのリンクがあります。悲しいことに、Connectの廃止により、多くの有用な資料が失われました(または見つけにくくなりました)。とにかく、MicrosoftのJim Hoggによる最も有用な引用は次のとおりです。
これは問題の核心に突き当たります-最適化はプログラムのセマンティクスを変更することを許可されますか?つまり、プログラムが特定の答えを出すが実行速度が遅い場合、クエリオプティマイザーがそのプログラムをより高速に実行すると同時に、与えられた結果も変更することは正当ですか?
「NO!」と叫ぶ前に(私自身の個人的な傾向も:-)、考慮してください:良いニュースは、99%のケースでは、答えは同じであることです。したがって、クエリの最適化は明らかに有利です。悪いニュースは、クエリに副作用のあるコードが含まれている場合、異なるプランが実際に異なる結果をもたらす可能性があることです。そして、NEWID()は、そのような違いを明らかにするそのような副作用(非決定的)「関数」の1つです。 [実際には、実験する場合、他のものを考案することができます-たとえば、AND句の短絡評価:2番目の句に算術ゼロ除算をスローさせる-異なる最適化により、最初の句の前に2番目の句が実行される可能性があります]これは、このスレッドの他の場所で、スカラ演算子がいつ実行されるかをSqlServerが保証しないというクレイグの説明。
したがって、選択肢があります。非決定的(副作用)コードの存在下で特定の動作を保証する場合-たとえば、JOINの結果が、ネストされたループ実行のセマンティクスに従うようにする場合- UCが指摘するように、適切なオプションを使用してその動作を強制できます。ただし、結果のコードの実行速度は遅くなります。これは、クエリオプティマイザーを無効にするためのコストです。
そうは言っても、クエリオプティマイザーはNEWID()の「期待どおり」の動作の方向に移動します-「期待どおりの結果」のパフォーマンスをトレードオフします。
時間の経過に伴うこの点での動作の変化の1つの例は NULLIFはRand() などの非決定的関数では正しく機能しません。他にも同様のケースがあります。 COALESCE
は、予期しない結果を生成する可能性があり、また徐々に対処されているサブクエリを使用します。
ジムは続けます:
ループを閉じる。 。 。この質問については、Devチームと話し合いました。そして、最終的には、次の理由により、現在の動作を変更しないことを決定しました。
1)オプティマイザーは、スカラー関数の実行のタイミングまたは数を保証しません。これは長い間定着してきた信条です。これは、オプティマイザがクエリプランの実行を大幅に改善するのに十分な自由を可能にする基本的な「余裕」です。
2)この「行ごとに1回の動作」は、あまり議論されていませんが、新しい問題ではありません。ユーコンのリリースでその動作を微調整し始めました。しかし、正確に、すべての場合において、それが意味することを正確に特定することは非常に困難です!たとえば、最終結果に「途中」で計算された中間行に適用されますか? -その場合、それは明らかに選択した計画に依存します。または、最終的に完了した結果に表示される行にのみ適用されますか? -あなたは同意するだろうと私は確信しているので、ここで起こっている厄介な再帰があります!
3)前に述べたように、デフォルトでは「パフォーマンスを最適化」します。これは99%のケースに適しています。結果が変化する可能性のあるケースの1%は、かなり簡単に特定できます-NEWIDなどの副作用の「関数」-簡単に「修正」できます(結果として、トレーディングのパフォーマンス)。この「パフォーマンスを最適化する」というデフォルトは、長く確立され、受け入れられています。 (はい、それは従来のプログラミング言語のコンパイラによって選択されたスタンスではありませんが、そうです)。
したがって、推奨事項は次のとおりです。
a)保証されていないタイミングと実行回数のセマンティクスへの依存を避けます。 b)表式の深いところでNEWID()を使用しないでください。 c)OPTIONを使用して特定の動作を強制する(トレーディングパフォーマンス)
この説明が、このバグを「修正しない」とクローズした理由を明確にするのに役立つことを願っています。
興味深いことに、
AND NOT (s_guid = NEWID())
は同じ実行プランを生成します
これは、クエリのコンパイルの非常に早い段階で行われる正規化の結果です。どちらの式もまったく同じ正規化形式にコンパイルされるため、同じ実行プランが作成されます。
これは(ここに)文書化されています:
クエリで指定された関数が実際に実行される回数は、オプティマイザによって作成された実行プランによって異なります。たとえば、WHERE句のサブクエリによって呼び出される関数です。サブクエリとその関数が実行される回数は、オプティマイザが選択したアクセスパスによって異なります。
これは、クエリプランがNEWID()を複数回実行して結果を変更する唯一のクエリフォームではありません。これは混乱を招きますが、NEWID()がキーの生成とランダムなソートに役立つようにするためには、実際には重要です。
最も混乱しやすいのは、すべての非決定的関数が実際にがこのように動作するわけではないということです。たとえば、Rand()とGETDATE()はクエリごとに1回だけ実行されます。
価値があるのは、これを見ると 古いSQL 92標準ドキュメント であり、不等式に関する要件はセクション "8.2 <comparison predicate>
"で次のように説明されています。
1)XとYを2つの対応する<行値コンストラクタ要素>にします。 XVとYVをそれぞれXとYで表される値とします。
[...]
ii)「X <> Y」は、XVとYVが等しくない場合にのみ当てはまります。
[...]
7)RxとRyを<比較述語>の2つの<行値コンストラクタ>とし、RXiとRYiをそれぞれRxとRyのi番目の<行値コンストラクタ要素>とします。 "Rx <comp op> Ry"は、次のようにtrue、false、または不明です。
[...]
b)一部のiのRXi <> RYiの場合に限り、「x <> Ry」はtrueです。
[...]
h)「x <> Ry」は、「Rx = Ry」がtrueの場合にのみfalseです。
注:<>
の比較について説明しているので、完全を期すために7bと7hを含めました-これが言っていることを大幅に誤解しているのでない限り、T-SQLで行値コンストラクタと複数の値の比較は実装されていないと思います-それはかなり可能です
これは混乱を招くごみの束です。しかし、ごみ箱をダイビングしたい場合は...
私は1.iiがこのシナリオに適用されるアイテムであると考えています。これは、「行値コンストラクタ要素」の値を比較しているためです。
ii) "X <> Y"は、XVとYVが等しくない場合にのみ真になります。
基本的に、XとYで表されるvaluesが等しくない場合、X <> Y
はtrueと言います。 X < Y OR X > Y
は、その述語を論理的に等価に書き換えたものであるため、オプティマイザがそれを使用することは完全に便利です。
標準では、<>
比較演算子のいずれかの側の行値コンストラクター要素の決定性(または何を取得しても)に関連するこの定義に制約を課していません。一方の値式が非決定的である可能性があるという事実に対処するのは、ユーザーコードの責任です。