私はピナル・デイブの このビデオ を見たところです。
彼には、tempdbで〜370kの読み取りを生成するSELECT
クエリと、クエリのSELECT
ingであるテーブルの〜1200の読み取りがあります。
次に、tempdbスプールを削除して、クエリのパフォーマンスを向上させるインデックス(これをIndex1
と呼びます)を作成します。これまでのところすべてOK。
ただし、その後、追加のインデックス(Index2
と呼びます)を作成し、Index1
をそのまま残します。
その後、クエリを再度実行すると、Index2
が使用されていなくても、クエリのパフォーマンスは元の状態に戻り、約370kのtempdbスプールがまだ残っています。
彼は実際にこれを引き起こす原因を説明していないようです(私が見逃した場合を除きます)
再現するコードは以下のとおりです(Pastebinを提供してくれたMartin Smithに感謝)これは、SalesOrderDetail
に標準インデックスを持つAdventureWorksのバニラバージョンを想定しています。
SET STATISTICS XML ON;
SET STATISTICS IO ON
GO
-- The query
DBCC FREEPROCCACHE;
SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID);
/*
(11110 rows affected)
Table 'Worktable'. Scan count 3, logical reads 368495, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table 'SalesOrderDetail'. Scan count 1, logical reads 1246, physical reads 2, page server reads 0, read-ahead reads 1284, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
*/
ここ は、何もする前の計画です(ネストされたループとテーブルスプール)。
次に、インデックスを作成します
CREATE NONCLUSTERED INDEX IX_Index1 ON Sales.SalesOrderDetail (SalesOrderID, ProductId) INCLUDE (SalesOrderDetailID, OrderQty);
その後、クエリを再度実行して、改善された計画とtempdbスプールがなくなったことを確認できます。
DBCC FREEPROCCACHE;
SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID);
/*
(11110 rows affected)
Table 'SalesOrderDetail'. Scan count 2, logical reads 608, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
*/
次に、別の(クエリには役に立たない)インデックスを作成します
CREATE NONCLUSTERED INDEX IX_Index2 ON Sales.SalesOrderDetail (ProductId,SalesOrderID) INCLUDE (SalesOrderDetailID, OrderQty);
次に、クエリを再度実行します。
-- Run the same query again
DBCC FREEPROCCACHE;
SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID);
/*
(11110 rows affected)
Table 'Worktable'. Scan count 3, logical reads 368495, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table 'SalesOrderDetail'. Scan count 1, logical reads 304, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
*/
読み取りと 計画 は、インデックスを追加する前と同じです。
IX_Index1を強制的に使用することもできます。
-- Run the query an force the index
DBCC FREEPROCCACHE;
SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod WITH (INDEX = IX_Index1)
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1 WITH (INDEX = IX_Index1)
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID);
/*
(11110 rows affected)
Table 'Worktable'. Scan count 3, logical reads 368495, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table '
そして、私はまだ spools plan を取得します
再び以下を守ったマーティン・スミスに感謝します。
IX_Index2
を作成してクエリを実行すると、不良プランが使用されます(クエリには役に立たないため、理にかなっています)。ただし、IX_Index1
を作成してクエリを実行すると、適切なプランはこのように使用すると、問題であるのは両方のインデックスが存在するのではなく、作成された順序であることが示唆されます。
私も このビデオ を見て、Pinalが同じ問題について提示しています。彼はインデックスがSELECT
のパフォーマンスを低下させることを示唆しているようで、この問題のために完全に削除する必要があります(私が誤解している場合を除きます)。
私の最初の質問は次のとおりです。何がこの動作を引き起こしますか?
私の2番目の質問は、この動作はどれほど一般的であるかです(2番目のビデオでは、インデックスをまったく作成しないことを提案した場合、かなり一般的だと思います)
ImplRestrRemap
クエリルールImplRestrRemap
は、インデックスが追加されたときにセカンダリクエリで使用されます。インデックスが追加されると、マージジョインよりもクエリバックでコストが低くなると推定されます。これは問題の結果です。特定のケースでマージ結合を使用するよりもサブツリーのコストが低い理由は、さらに下にあります。
トレースフラグを追加して出力ツリーをチェックすると、このルールの使用法を見つけることができます。
_OPTION(
RECOMPILE,
QUERYTRACEON 3604,
QUERYTRACEON 8607)
_
クエリにOPTION(QUERYRULEOFF ImplRestrRemap)
を追加して、ルールを無効にすることができます。
_SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID)
OPTION
(
QUERYRULEOFF ImplRestrRemap
);
_
これにより、マージ結合の代わりにハッシュ結合が提供され、スプールプランよりも推定されるサブツリーのコストが高くなります。もう1つの違いは、1つに2回アクセスする代わりに、作成された2つの非クラスター化インデックスを使用することです。
マージ結合
マージ結合をプランに追加してルールを無効にしようとすると、クエリプランに並べ替えが追加されます。
_OPTION
(
QUERYRULEOFF ImplRestrRemap,
MERGE JOIN
);
_
推定されるサブツリーコストはハッシュ結合よりも高く、このハッシュ結合の推定されるサブツリーコストは、スプールプランよりも高くなります。ヒントなしで計画の選択を説明する。
ここでも、作成された2つのNCインデックスを使用しています。
インデックスのヒント
両方のテーブルアクセスで同じインデックスを使用して「サンプル」プランを取得しようとする場合は、インデックスヒントを追加できます。
_SELECT SalesOrderID, ProductId,SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail sod WITH(INDEX(IX_SalesOrderID_ProductId))
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail sod1 WITH(INDEX(IX_SalesOrderID_ProductId))
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID)
OPTION
(
QUERYRULEOFF ImplRestrRemap,
MERGE JOIN
);
_
これにより、別のソート演算子が追加されます。
結合方向
マージ結合の内側と外側の結合列が入れ替えられるため
ソートマージ結合実行プランなし
_<InnerSideJoinColumns>
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod]" Column="SalesOrderID" />
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod]" Column="ProductID" />
</InnerSideJoinColumns>
<OuterSideJoinColumns>
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod1]" Column="SalesOrderID" />
<ColumnReference Column="Expr1002" />
</OuterSideJoinColumns>
_
ダブルソートマージ結合実行プラン
_<InnerSideJoinColumns>
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod]" Column="ProductID" />
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod]" Column="SalesOrderID" />
</InnerSideJoinColumns>
<OuterSideJoinColumns>
<ColumnReference Column="Expr1002" />
<ColumnReference Database="[AdventureWorks2017]" Schema="[Sales]" Table="[SalesOrderDetail]" Alias="[sod1]" Column="SalesOrderID" />
</OuterSideJoinColumns>
_
パフォーマンスの高いクエリプランは、次のインデックスキーパスを通過できるという意味で
--> SalesOrderId - SalesOrderId --> ProductId - ProductId (Agg)
並べ替えクエリプランは従うことができますが
--> ProductId (Agg) - ProductId --> SalesOrderId - SalesOrderId
また、追加されたソートが原因で、ダブルソートマージ結合プランを使用すると、インデックススキャンは順序付けられません。
_ <IndexScan Ordered="false" ForcedIndex="true" ForceSeek="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
_
パフォーマンスの良いクエリプランを強制しようとしています
正しいプランを強制しようとするとき、たとえば、問題を提供しない順序でインデックスを作成してからクエリを実行し、プランxmlをキャプチャする場合:
_drop index if exists IX_ProductId_SalesOrderID on Sales.SalesOrderDetail;
drop index if exists IX_SalesOrderID_ProductId on Sales.SalesOrderDetail;
CREATE NONCLUSTERED INDEX IX_ProductId_SalesOrderID ON Sales.SalesOrderDetail (ProductId,SalesOrderID) INCLUDE (SalesOrderDetailID, OrderQty);
CREATE NONCLUSTERED INDEX IX_SalesOrderID_ProductId ON Sales.SalesOrderDetail (SalesOrderID, ProductId) INCLUDE (SalesOrderDetailID, OrderQty);
_
次に、そのプランXMLを使用して、インデックスを別の順序で再作成します。
_drop index if exists IX_ProductId_SalesOrderID on Sales.SalesOrderDetail;
drop index if exists IX_SalesOrderID_ProductId on Sales.SalesOrderDetail;
CREATE NONCLUSTERED INDEX IX_SalesOrderID_ProductId ON Sales.SalesOrderDetail (SalesOrderID, ProductId) INCLUDE (SalesOrderDetailID, OrderQty);
CREATE NONCLUSTERED INDEX IX_ProductId_SalesOrderID ON Sales.SalesOrderDetail (ProductId,SalesOrderID) INCLUDE (SalesOrderDetailID, OrderQty);
_
_USE PLAN
_ HINTは、同じクエリと同じインデックスのエラーを報告します。
メッセージ8698、レベル16、状態0、行1クエリプロセッサはクエリプランを作成できませんでした。USEPLANヒントにクエリに対して正当であると確認できなかったプランが含まれているためです。 USE PLANヒントを削除または置き換えます。プランの強制が成功する可能性を最大限に高めるには、USE PLANヒントで提供されるプランが、同じクエリに対してSQL Serverによって自動的に生成されるものであることを確認してください。
マージ結合で同じ並べ替え順序を使用することは不可能であり、マージ結合の最適なオプションとして並べ替え演算子を使用するには元に戻す必要があります。
これとは逆に、パフォーマンスの良いマージ結合プランを使用できる場合に、_USE PLAN
_ヒントを使用してスプールプランをクエリに適用することは実際に可能です。
これで取り上げられたポイント Q/A これがなぜ起こっているのかについて、より多くの情報を提供します。
並べ替え
前述の Q/A で指定されているとおりに順序を追加すると、インデックスの作成順序が異なる場合でも、正しい実行プランが選択されます。
_SELECT SalesOrderID, ProductId
FROM Sales.SalesOrderDetail2 sod
WHERE ProductID = (SELECT AVG(ProductID)
FROM Sales.SalesOrderDetail2 sod1
WHERE sod.SalesOrderID = sod1.SalesOrderID
GROUP BY sod1.SalesOrderID
)
ORDER BY SalesOrderID;
_