次のようなクエリで問題が発生しています。
SELECT COUNT('A') FROM [dbo].[OINV] T0
INNER JOIN [dbo].[OCRD] T2 ON T2.[CardCode] = T0.[CardCode]
WHERE T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
ヒットするインデックスは次のように定義されます。
NONCLUSTERED INDEX [OCRD_FATHER] ON [dbo].[OCRD]
(
[FatherCard] ASC
) INCLUDE (CardCode)
NONCLUSTERED INDEX [OINV_CUSTOMER] ON [dbo].[OINV]
(
[CardCode] ASC
)
現在、実行に1〜2秒かかっており、0のカウントが返されます(これは期待されていることです)。
ハッシュマッチにフィードされる前に非クラスター化インデックスがフィルター処理されないことに驚かされます。すべての行にフィードされます。これらはベンダーソフトウェアクエリであるため、残念ながらそれらを書き換える方法はありません。
これはなぜですか?クエリを書き直さずにハッシュマッチを実行する前にフィルターに変更する方法はありますか?
データ設定例
CREATE TABLE [dbo].[OCRD]
(
[FatherCard] NVARCHAR(50),
[CardCode] NVARCHAR(50)
);
INSERT INTO [dbo].[OCRD]
SELECT TOP (2076000) NEWID(),
NEWID()
FROM master..spt_values v1,
master..spt_values v2,
master..spt_values v3
CREATE TABLE [dbo].[OINV]
(
[CardCode] NVARCHAR(50)
)
INSERT INTO [dbo].[OINV]
SELECT TOP (5175460) NEWID()
FROM master..spt_values v1,
master..spt_values v2,
master..spt_values v3
CREATE NONCLUSTERED INDEX [OCRD_FATHER]
ON [dbo].[OCRD] ( [FatherCard] ASC )
INCLUDE (CardCode)
CREATE NONCLUSTERED INDEX [OINV_CUSTOMER]
ON [dbo].[OINV] ( [CardCode] ASC )
どうしてこれなの
これは、述語を暗示するSQL Serverの機能に対する制限のように見えます。
変更した場合
_WHERE T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
に
_WHERE T2.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
次に、述語が_T2
_のスキャンにプッシュされ、パフォーマンスが大幅に向上します。 _T2.[CardCode] = T0.[CardCode]
_の結合条件により、2つが等しいことが保証されているため、セマンティクスは変更されません。
質問に追加したサンプルデータとoption (hash join)
ヒントを使用して、元のバージョンにあった
_CPU time = 12434 ms, elapsed time = 3597 ms.
_
私のマシンと2番目のバージョン
_CPU time = 405 ms, elapsed time = 580 ms.
_
暗黙の述語がここで失敗する理由の詳細はわかりません。ただし、OR
は役割を果たすようです。 AND
- ed述語の場合
_T0.[CardCode] = (@P2) AND T2.[FatherCard] = (@P3)
_
_T2
_のスキャンに問題なく適用できます。
_SELECT COUNT('A') FROM [dbo].[OINV] T0
INNER JOIN [dbo].[OCRD] T2 ON T2.[CardCode] = T0.[CardCode]
WHERE T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
include ._Covering index
_はこのクエリの観点から削除する必要があります。
私はあなたがクエリを変更することはできませんが、データ型とそのnull機能のヘルプとともにテーブル構造をスローすることはできないことを知っています。
COUNT('A')
がわかりにくいのはなぜですか?Count(*)
またはcount(CardCode)
である必要があります。
インデックススキャンの1つ_OCRD_FATHER
_推定行は_2076000
_です
別のインデックススキャンはOINV_CUSTOMER]は_5175460
_
簡単に言うと、多数の行が返されるか、または_cardianility estimate
_が高い場合、オプティマイザはインデックスを使用するよりもスキャンすることを決定します。
したがって、インデックススキャンを取得しています。
結合のいずれかで返される行数が少ない場合は、ハッシュ一致結合が非常に望ましく明白でした。
クエリプランから_Bitmap filter
_が返す行数は明確ではありません。_Bitmap filter
_の直後の矢印のツールヒントから確認できます。 _2076000
_よりもはるかに少ない行を返す必要があります
現在の状況では、両方とも非常に高い推定行を示しているため、ハッシュ一致は望ましくありません。
これは古い統計が原因である可能性があります。
ビットマップフィルターが返す行数がはるかに少ない場合は、ハッシュマッチジョインが明らかです。
ビットマップフィルター:ビットマップの主な役割は、クエリの早い段階で、行がParallelism演算子を介して渡される前にセミジョイン削減を実行することにより、並列プランを高速化することです。
クエリを書き換えることはできないため、ヒントやその他の方法を使用することはできません。
ここでは、XML計画はグラフィカルな計画よりも多くのことを明らかにします。
XML計画はCONVERT_IMPLICIT(int,[globalagg1006],0)
があることを明らかにします
述語の一部が原因で_Residual Probe
_の下にある:データ型間の_inequality operator or implicit conversion
_が原因で残差プローブが発生します。残差プローブ述語ははるかに後でフィルター処理されます。残差述語はメイン述語の後に実行されます。残差述語は存在しない場合がありますツールチップ。ただし、それは_xml plan
_または_property window
_にあります(F4を押します)。
これは、クエリが非常に遅い理由でもあります。
_T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
ここで、@ P2データ型がT0。[CardCode]に類似していること、および@ P3データ型がT2。[FatherCard]に類似していることを確認してください。
同様に、_T2.[CardCode] = T0.[CardCode]
_データ型も同様です。
更新1
@Martinサンプルデータに従って、
クエリ1
_declare @P2 nvarchar(50)
declare @P3 nvarchar(50)
SELECT COUNT('A') FROM [dbo].[OINV] T0
INNER JOIN [dbo].[OCRD] T2 ON T2.[CardCode] = T0.[CardCode]
WHERE T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
約17秒かかります。
T2[CardCode] = (@P2)
に変更すると、1秒未満かかります。インデックススキャンからシークへの変更も計画します。
このような大幅な変更の理由の1つは、データ型です。
次に、クエリ2、パラメーターのデータ型を変更します
_declare @P2 varchar(50)
declare @P3 varchar(50)
SELECT COUNT('A') FROM [dbo].[OINV] T0
INNER JOIN [dbo].[OCRD] T2 ON T2.[CardCode] = T0.[CardCode]
WHERE T0.[CardCode] = (@P2) OR T2.[FatherCard] = (@P3)
_
実行には6秒かかります。実際のデータでは、結果を生成する場合、さらに時間がかかります。
私の実行計画は@Zacの実行計画とほとんど同じです。テーブルスキャン、3つの並列処理、bitMapの作成、述語の残差とハッシュプローブの残差によるハッシュ結合を取得しています。
Where条件をT2.[CardCode] = (@P2)
に変更した場合
1秒かかり、クエリプランにいくつかの変更があります。
ネストされたT2へのハッシュ結合の変更はインデックスシークです。T0は依然としてテーブルスキャンであり、並列処理を生成します。
したがって、データタイプ、残差などに関する上記の「更新」の私の答えは正しいです。
そして、これは@Zacの場合に何が起こっているのか。
この質問の特定のケースで機能しない理由はありませんが、一般に、SQLは結果が正しいことを確認するために両方のデータセットのすべての行で結合を実行する必要があります。
この場合、両方のテーブルからCardCodeを取得して結合しているだけなので、結果を簡単に結合できますが、異なる結果セットの場合は機能しません。
例えば。より一般的なケース:
SELECT T0.CustomerName, T1.OrderID
FROM Customer T0
LEFT JOIN Order T1 ON T1.CustomerID = T0.CustomerID
WHERE T0.IsActive = 'Y' OR T1.IsCancelled = 'N'
この例では、IsCancelled
述語を満たすOrdersが存在する可能性がありますが、一致するCustomerはIsActive
述語を満たしていない可能性があります。テーブル。これは、完全スキャンを実行し、結合が発生する前にフィルタリングを行わない理由です。