追加7/11問題は、MERGE JOIN中のインデックススキャンが原因でデッドロックが発生することです。この場合、トランザクションはFK親テーブルでインデックス全体のSロックを取得しようとしますが、以前は別のトランザクションがインデックスのキー値にXロックをかけています。
小さな例から始めましょう(70-461コースのTSQL2012 DBが使用されています):
CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )
列[custid], [empid], [shipperid]
は、[Sales].[Customers], [HR].[Employees], [Sales].[Shippers]
それに応じて相互に関連するパラメーターです。いずれの場合も、親テーブルの参照列にクラスター化インデックスがあります。
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])
外部キーを除いて同じ構造を持つINSERT [Sales].[Orders] SELECT ... FROM
別のテーブルを呼び出そうと[Sales].[OrdersCache]
してい[Sales].[Orders]
ます。テーブルについて言及するもう1つの重要な点[Sales].[OrdersCache]
は、クラスター化インデックスです。
CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )
予想通り、少量のデータを挿入しようとすると、LOOP JOINは外部キーでインデックスシークを正常に実行します。
大量のデータがある場合、MERGE JOINはクエリオプティマイザーでクエリのforennキーを維持する最も効率的な方法として使用されます。
また、外部キーを使用する場合はOPTION(LOOP JOIN)、明示的なJOINの場合はINNER LOOP JOINを使用することを除いて、それとは関係ありません。
以下は、私の環境で実行しようとしているクエリです。
INSERT Sales.Orders (
custid, empid, shipperid, ... )
SELECT custid, empid, 2, ...
FROM Sales.OrdersCache
プランを見ると、MERGE JOINで検証された3つすべてのフォアキーがわかります。これは、インデックス全体をロックするINDEX SCANを使用するため、私には適切な方法ではありません。
OPTION(LOOP JOIN)の使用は、MERGE JOINよりも約15%高いため、適切ではありません(データ量が増えると、回帰は大きくなると思います)。
SELECTステートメントshipperid
では、挿入されたセット全体の属性の単一の値を確認できます。私の意見では、挿入されたセットの検証フェーズを少なくとも不変の属性に対してより高速にする方法がなければなりません。何かのようなもの:
- JOIN検証の未定義のサブセットがある場合は、LOOP JOIN、MERGE JOIN、HASH JOINを作成します。
- 検証された列の明示的な値が1つしかない場合は、検証を1回だけ行います(INDEX SEEK)。
コード構造、追加のDDLオブジェクトなどを使用して、上記の状況を回避する一般的なパターンはありますか?
20/07を追加。解決。Query Optimizerは、MERGE JOINを使用して、「単一キー-外部キー」の検証最適化をすでに行っています。また、Sales.Shippersテーブルのみを作成し、LOOP JOINをクエリ内の別の結合に同時に残します。親テーブルにいくつかの行があるので、クエリオプティマイザーはソート/マージ結合アルゴリズムを使用して、内部テーブルの各行を親テーブルと1回だけ比較します。したがって、単一のキーの検証中にセット内の単一の値を効果的に処理する特定のメカニズムがある場合、これが私の質問の答えです。それはそれほど完璧な決定ではありませんが、SQL Serverがケースを最適化する方法です。
パフォーマンスへの影響の調査により、私の場合、MERGE JOINとLOOP JOINのINSERTステートメントは、同時に挿入された750行とほぼ等しくなり(MERGE JOIN(CPU時間リソースで))優れています。したがって、OPTION(LOOP JOIN)を使用することは、私のビジネスプロセスにとって適切なソリューションです。