SQL ServerはA <> BをA <B OR A> Bに分割し、Bが非決定的である場合に奇妙な結果をもたらします


26

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行が返されますが、時々(非常に頻繁に、10回のうち1回以上)0行が返されます。私のシステムでSQL Server 2008を使用して複製しました。上記のリンク(SQL Server 2014)を使用してオンラインで複製できます。

実行プランを見ると、クエリアナライザーは明らかに条件をs_guid < NEWID() OR s_guid > NEWID()次のように分割していることがわかります。

クエリプランのスクリーンショット

...これが失敗する理由を完全に説明します(最初に生成されたIDが小さく、2番目のIDが指定されたIDよりも大きい場合)。

式の1つが非決定的であっても、SQL ServerはA <> Bとして評価できA < B OR A > Bますか?はいの場合、どこに文書化されていますか?それともバグを見つけましたか?

興味深いことに、AND NOT (s_guid = NEWID())同じ実行計画(および同じランダムな結果)が得られます。

開発者がオプションで特定の行を除外して使用したいときにこの問題を発見しました:

s_guid <> ISNULL(@someParameter, NEWID())

以下の「ショートカット」として:

(@someParameter IS NULL OR s_guid <> @someParameter)

ドキュメントやバグの確認を探しています。コードはそれほど適切ではないため、回避策は不要です。


4
この質問に似ているようです:乱数と結合タイプの予期しない結果
エリックダーリン

回答:


22

式の1つが非決定的であっても、SQL ServerはA <> Bとして評価できA < B OR A > Bますか?

これはやや議論の余地がある点であり、答えは「はい」です。

Itzik Ben-GanのConnectバグレポートBug with NEWIDおよびTable Expressionsに対する回答で、私が知っている最高の議論が行われました。Connectは廃止されたため、Webアーカイブへのリンクがあります。悲しいことに、Connectの終byにより、多くの有用な資料が失われました(または見つけにくくなりました)。とにかく、MicrosoftのJim Hoggからの最も有用な引用は次のとおりです。

これが問題の核心に当たります-最適化によりプログラムのセマンティクスを変更できますか?すなわち、プログラムが特定の回答を生成するが、実行速度が遅い場合、クエリオプティマイザーがそのプログラムをより高速に実行する一方で、結果を変更することは合法ですか?

「NO!」と叫ぶ前に (私自身の個人的な傾向:-)、考慮してください:良いニュースは、99%のケースで、答えが同じであることです。したがって、クエリの最適化は明らかな勝利です。悪いニュースは、クエリに副作用のあるコードが含まれている場合、異なる計画によって異なる結果が得られる可能性があることです。そして、NEWID()は、そのような副作用(非決定的)の「関数」であり、違いを明らかにします。[実際、実験すれば、他の手段を考案できます。たとえば、AND節の短絡評価:2番目の節に算術ゼロ除算をスローします-異なる最適化により、最初の節の前に2番目の節を実行できます]このスレッドの他の場所でのCraigの説明では、いつSqlServerがスカラー演算子が実行されるかを保証しません。

したがって、選択肢があります:非決定的(副作用)コードの存在下で特定の動作を保証したい場合-たとえば、JOINの結果がネストされたループ実行のセマンティクスに従うように-そしてUCが指摘するように、適切なオプションを使用してその動作を強制できます。ただし、結果のコードの実行速度は遅くなります。これは、クエリオプティマイザーをホブリングするコストです。

とはいえ、クエリオプティマイザーは、NEWID()の「期待どおり」の動作の方向に移動しています。「期待どおりの結果」とパフォーマンスのトレードオフです。

この点に関する時間の経過に伴う動作の変化の1つの例は、NULLIFがRAND()などの非決定的関数で正しく動作しないことです。またCOALESCE、予期しない結果を生む可能性のあるサブクエリなどを使用する同様のケースもあり、これらも徐々に対処されています。

ジムは続けます:

ループを閉じます。。。この質問については、開発チームと話し合いました。そして、次の理由により、現在の動作を変更しないことにしました。

1)オプティマイザーは、スカラー関数の実行のタイミングまたは数を保証しません。これは、長期にわたって安定した信条です。これは、オプティマイザがクエリプランの実行を大幅に改善するための十分な自由度を可能にする基本的な「余裕」です。

2)この「行ごとの動作」は新しい問題ではありませんが、広く議論されていません。ユーコンのリリースでその動作を微調整し始めました。しかし、すべての場合において、正確に何を意味するかを正確に突き止めることは非常に困難です!たとえば、最終結果の「途中」で計算された中間行に適用されますか?-この場合、選択した計画に明らかに依存します。または、最終的に完成した結果に表示される行にのみ適用されますか?-あなたが同意するだろうと確信して、ここで起こっている厄介な再帰があります!

3)前に述べたように、デフォルトでは「パフォーマンスを最適化」します-これは99%のケースに適しています。結果を変更する可能性のあるケースの1%は、かなり簡単に見つけることができます-NEWIDなどの副作用のある「機能」-「修正」するのは簡単です(結果としてパフォーマンスのトレーディング)。このデフォルトの「パフォーマンスの最適化」が再び設定され、長期にわたって確立され、受け入れられています。(はい、それは従来のプログラミング言語用にコンパイラが選択したスタンスではありませんが、そうです)。

したがって、推奨事項は次のとおりです。

a)保証されていないタイミングと実行回数のセマンティクスへの依存を避けます。b)NEWID()をテーブル式で深く使用しないでください。c)OPTIONを使用して特定の動作を強制する(トレーディングパフォーマンス)

この説明が、このバグを「修正できない」としてクローズする理由を明確にするのに役立つことを願っています。


興味深いことに、AND NOT (s_guid = NEWID())同じ実行計画が得られます

これは、クエリのコンパイル中の非常に早い段階で発生する正規化の結果です。両方の式は、まったく同じ正規化形式にコンパイルされるため、同じ実行計画が作成されます。


この場合、問題を回避するように思われる特定の計画を強制する場合は、WITH(FORCESCAN)を使用できます。確かに、クエリを実行する前に変数を使用してNEWID()の結果を保存する必要があります。
ラズバンソコル

11

これはここで(並べ替えて)文書化されています:

クエリで指定された関数が実際に実行される回数は、オプティマイザーによって作成された実行プランによって異なります。例は、WHERE句のサブクエリによって呼び出される関数です。サブクエリとその関数が実行される回数は、オプティマイザーによって選択されたアクセスパスによって異なる場合があります。

ユーザー定義関数

これは、クエリプランがNEWID()を複数回実行して結果を変更する唯一のクエリフォームではありません。これは紛らわしいですが、NEWID()がキーの生成とランダムなソートに役立つために実際に重要です。

最も紛らわしいのは、すべての非決定的関数が実際にこのよう動作するわけではないということです。たとえば、RAND()およびGETDATE()はクエリごとに1回だけ実行されます。


エンジンが「等しくない」を範囲に変換する理由/タイミングを説明するブログ投稿などはありますか?
ミスターマグー

3
私が知っていることではありません。、、、およびBTreeに対して効率的に評価できるため=、ルーチンになる場合があります。<>
デビッドブラウン-マイクロソフト

5

この古い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)「x <> Ry」は、iのRXi <> RYiの場合にのみ真です。

[...]

h)「x <> Ry」は、「Rx = Ry」がtrueの場合にのみfalseです。

注:完全性のために7bと7hを含めましたが、それらは<>比較について話しているためです-複数の値を持つ行値コンストラクターの比較は、これが言っていることを大まかに誤解していない限り、T-SQLに実装されているとは思いません-これはかなり可能です

これは紛らわしいゴミの集まりです。しかし、ごみ箱ダイビングを続けたい場合は...

私が考えること1.iiは、我々はの値を比較していることから、このシナリオに適用される項目である「行値コンストラクタ要素を。」

ii)「X <> Y」は、XVとYVが等しくない場合にのみ真です。

基本的にX <> Yは、XとYで表されるが等しくない場合はtrue と言います。以来X < Y OR X > Yその述語の論理的に等価書き直したものですオプティマイザがそれを使用するために、それは完全にクールです。

標準では、<>比較演算子の両側の行値コンストラクター要素の決定性(またはそれを取得するもの)に関連するこの定義に制約を設けていません。一方の値式が非決定的である可能性があるという事実に対処するのは、ユーザーコードの責任です。


1
私は投票(上または下)から遠ざかるでしょうが、私は確信していません。あなたが提供する引用は「値」に言及しています。私の理解では、比較は2つの値の間で行われ、各側に1つずつあります。各側の値の2つ(またはそれ以上)のインスタンス化の間ではありません。さらに、標準(少なくとも引用する92)では、非決定的関数については一切言及していません。あなたと同様の推論により、標準に準拠するSQL製品は非決定的な機能を提供せず、標準で言及されている機能のみを提供すると想定できます。
ypercubeᵀᴹ

@yperフィードバックをありがとう!あなたの解釈は間違いなく有効だと思います。このドキュメントを読んだのはこれが初めてです。「行値コンストラクター」で表される値のコンテキストで値に言及しています。これは、ドキュメント内の他の場所で(他の多くのものの中でも)スカラーサブクエリになります。特にスカラーサブクエリは、非決定的である可能性があるようです。しかし、私は何について話しているのか本当にわかりません=)
ジョシュダーネル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.