次のSQL Serverクエリを作成しましたが、SQL Server 2005で anti-semi join欠陥 が発生し、カーディナリティの推定が不正確になり(1-urgh!)、永久に実行されます。これは長年の運用SQLサーバーであるため、バージョンのアップグレードを簡単に提案することはできません。そのため、この特定のクエリに対してトレースフラグ4199ヒントを強制することはできません。
WHERE AND NOT IN (SELECT)
のリファクタリングに苦労しています。誰でも手伝ってくれる?クラスタ化されたキーペアに基づいて、最適な結合を使用してみてください。
SELECT TOP 5000 d.doc2_id
,d.direction_cd
,a.address_type_cd
,d.external_identification
,s.hash_value
,d.publishdate
,d.sender_address_id AS [D2 Sender_Address_id]
,a.address_id AS [A Address_ID]
,d.message_size
,d.subject
,emi.employee_id
FROM assentor.emcsdbuser.doc2 d(NOLOCK)
INNER JOIN assentor.emcsdbuser.employee_msg_index emi(NOLOCK)
ON d.processdate = emi.processdate
AND d.doc2_id = emi.doc2_id
INNER LOOP JOIN assentor.emcsdbuser.doc2_address a(NOLOCK)
ON emi.doc2_id = a.doc2_id
AND emi.address_type_cd = a.address_type_cd
AND emi.address_id = a.address_id
INNER JOIN sis.dbo.sis s(NOLOCK) ON d.external_identification = s.external_identification
WHERE d.publishdate > '2008-01-01'
**AND d.doc2_id NOT IN (
SELECT doc2_id
FROM assentor.emcsdbuser.doc2_address d2a(NOLOCK)
WHERE d.doc2_id = d2a.doc2_id
AND d2a.address_type_cd = 'FRM'
)**
OPTION (FAST 10)
Employee_MSG_Index
テーブルは500m行、doc2
は1.5b行、SIS
は〜500m行です。
何か助けていただければ幸いです!
これは長年の運用SQL Serverであるため、バージョンのアップグレードを簡単に提案することはできません
反準結合カーディナリティの推定バグは、SQL Serverのすべてのバージョンで再現可能です2005から2012まで。修正を有効にするには、すべてトレースフラグ4199が必要であるため、アップグレードでは4199をアクティブ化しないと問題を解決できません(もちろん、2005からアップグレードする理由は他にもたくさんあります)。
...そのため、この特定のクエリに対してトレースフラグ4199ヒントを強制することはできません。
影響を受ける特定のクエリが1つだけある場合、OPTION (QUERYTRACEON 4199)
を使用して、そのクエリのみのトレースフラグを有効にすることができます。このクエリヒントは4199で使用するための ドキュメント化およびサポート であり、SQL Server 2005 Service Pack 2以降に適用されます。
このヒントは、クエリの周りでDBCC TRACEON (4199)
およびDBCC TRACEOFF (4199)
を効果的に実行し、結果としてsysadmin権限が必要です。それが問題である場合は、 計画ガイド を使用してヒントを追加します。
4199を有効にしたシステム全体のテストinstance-wideも確認する必要があります。計画の回帰は可能ですが、全体として、このフラグによって有効化されるさまざまなオプティマイザの修正は、それだけの価値があることに気付くでしょう。今後の計画に影響するすべてのクエリプロセッサの修正 このフラグが必要 をアクティブ化します。
ypercubeの回答 で述べたように、このバグでは、(詳細の中でも)マニフェストを作成するために2つ以上の結合列が必要です。 _NOT IN
_句のredundancyにより、オプティマイザは2つの列の比較(論理的には1つしかありません)、それによってバグを公開します。
この冗長性を削除すると、この特定のクエリの問題が「解決」されますが、実際に複数の結合述語を持つ他のクエリは依然として脆弱です。
説明のために、質問にリンクされたCSSブログ投稿に基づく例を次に示します(ただし、完全なスクリプトを使用します)。
_CREATE TABLE dbo.tst_TAB1
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE TABLE dbo.tst_TAB2
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE INDEX i ON dbo.tst_TAB1 (c1, c2);
CREATE INDEX i ON dbo.tst_TAB2 (c1, c2);
_
サンプルデータ:
_INSERT dbo.tst_TAB1
(c1, c2, c3)
SELECT
number, number, number
FROM master.dbo.spt_values
WHERE
[type] = N'P'
AND number BETWEEN 1 AND 2047;
INSERT dbo.tst_TAB2 (c1, c2, c3)
VALUES (1, 1, 1);
_
冗長な述語で_NOT IN
_を使用してクエリをテストします。
_SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
-- This is redundant!
WHERE
t2.c1 = t1.c1
);
_
推定実行プランは、反準結合後の1行の推定を示しています。
サイドノート:実際、これは別の(まれな)バグの例です。 _t1.c1 = t2.c1
_ではなく_t2.c1 = t1.c1
_としてWHERE
句を記述すると、オプティマイザは2つの結合述語が実際に同じであり、バグが発生しないことを確認できます。
OPTION (QUERYTRACEON 4199)
と同じクエリ:
_SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
)
OPTION (QUERYTRACEON 4199);
_
推定実行プランは、2046行の推定値を表示しますが、これは正確です。
冗長な述語を削除することもできます:
_SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
);
_
実行計画では偶然、関連のない追加の最適化(Stream Aggregate)が使用されていますが、重要な点は、4199を有効にする必要なしに、結合後の推定が正しいことです。
_NOT IN
_構文を使用して、複数の列に対する反準結合を表すことができます。これらのケースでは4199が必要です。たとえば、次のクエリは_c1
_および_c2
_で結合します。
_SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
);
_
実行計画は、誤った1行の見積もりを示しています。
4199で、問題は解決しました:
_SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
)
OPTION (QUERYTRACEON 4199);
_
この方法で_NOT IN
_を使用することは、特にBooks Onlineに記載されている理由から、避けるのが最善です。
_NOT IN
_とNULLs
に関するこの問題は 約 で何度も発生しています。利用可能な多くの代替構文があり、そのうち_NOT EXISTS
_は私の個人的な好みです。構文を変更しても、カーディナリティ推定のバグは回避されないことに注意してください。
_SELECT
T1.c1
FROM dbo.tst_TAB1 AS t1
WHERE
NOT EXISTS
(
SELECT 1
FROM dbo.tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
AND t2.c2 = t1.c2
);
_
その2列の反セミ結合は1行の見積もりを生成し、それを修正するには4199が必要です。実行計画は前に見たものとまったく同じなので、繰り返しはしません。 _NOT EXISTS
_構文は、_NOT IN
_のNULLs
問題を回避します。
Ypercubeの他の観察に同意します。
クエリ内のすべてのテーブルにNOLOCK
ヒントを振りかけるのは、コードの悪臭です。クエリが_READ UNCOMMITTED
_トランザクションセマンティクスを本当に許容できる場合は、分離レベルを明示的に設定します。
_ORDER BY
_なしのTOP
は、コードが不十分であることを示すもう1つの兆候です。 TOP
は、TOP
の意味を定義するために_ORDER BY
_句を必要とします。観察された動作に決して依存せず、明示的なトップレベルの_ORDER BY
_を使用して保証を取得してください。
_INNER LOOP JOIN
_および結合ヒントは、一般に_FORCE ORDER
_クエリヒントを意味します。これはオプティマイザの自由を厳しく制限し、通常は誤解され、誤って適用されます。完全に理解していないヒントを使用しないでください。
あなたが提供したリンクは、バグが複数の列を持つ結合にのみ影響を与えると言っています
上記の例のように、複数の結合列が結合に含まれている場合にのみ、この問題が発生することに注意してください。
そして、なぜあなたがこのようにNOT IN
を書き込んだのか(サブクエリにd.doc2_id = d2a.doc2_id
条件を追加している)は理解できません。 NOT IN
は次のように書くことができます:
AND d.doc2_id NOT IN (
SELECT d2a.doc2_id
FROM assentor.emcsdbuser.doc2_address d2a
WHERE d2a.address_type_cd = 'FRM'
)
またはNOT EXISTS
を使用:
AND NOT EXISTS (
SELECT 1
FROM assentor.emcsdbuser.doc2_address d2a
WHERE d.doc2_id = d2a.doc2_id
AND d2a.address_type_cd = 'FRM'
)
両方を試して、カーディナリティの見積もりの問題が解決したかどうかを確認してください。
その他の注意事項:
address_type_cd
のインデックスはありますか?NOLOCK
を使用するのですか?TOP
なしでORDER BY
を指定しないと、実行ごとに異なる結果が得られる場合があります。