FOREIGN KEYに明示的な単一のKEY値を持つMERGE JOIN(INDEX SCAN)を克服する


9

追加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を使用するため、私には適切な方法ではありません。 外部キー検証中のMERGE JOIN

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)を使用することは、私のビジネスプロセスにとって適切なソリューションです。

回答:


8

OPTION(LOOP JOIN)の使用は、MERGE JOINよりも約15%高いため、適切ではありません。

showplanの出力に表示されるコストの割合は、実行後の(実際の)計画であっても、常にオプティマイザモデルの推定値です。これらのコストは、特定のハードウェアでの実際のランタイムパフォーマンスを反映していない可能性があります。確実にする唯一の方法は、ワークロードで代替案をテストし、最も重要なメトリック(経過時間、CPU使用率など)を測定することです。

私の経験では、オプティマイザは、ループ結合からマージ結合に切り替えて、外部キー検証の早すぎるタイミングで切り替えています。最大の操作を除くすべてにおいて、ループ結合が望ましいことがわかりました。そして、「大規模」とは、少なくとも数千万行を意味し、確かに数千行ではなく、質問へのコメントで示します。

私の意見では、挿入されたセットの検証フェーズを少なくとも不変の属性に対してより高速にする方法がなければなりません。

これは原則的には理にかなっていますが、現在のところ、外部キー検証が使用する計画構築ロジック内にはそのようなロジックはありません。現在のロジックは意図的に非常に一般的で、より広いクエリに直交しています。特定の最適化はテストを複雑にし、エッジケースのバグの可能性を高めます。

コード構造、追加のDDLオブジェクトなどを使用して、上記の状況を回避する一般的なパターンはありますか?

私が知っていることではありません。マージ結合外部キー検証のデッドロックリスクはよく知られており、最も広く使用されている回避策がOPTION (LOOP JOIN)ヒントになります。外部キーの検証中に共有ロックが取得されるのを防ぐ方法はありません。これらは、行バージョンの分離レベルでも、正確さのために必要なためです

複数の同時プロセスが親テーブルと子テーブルにトランザクションで行を追加する機能を保持したい場合、(ループ結合ヒントよりも)一般的な答えはありません。ただし、データの変更(読み取りではない)をシリアル化する場合は、sp_getapplockを使用すると、信頼性が高く、簡単な方法です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.