1

私は、「パブリッシャー」アプリケーションが、非常に複雑なビューを照会し、個別の挿入、更新、および削除操作を使用して、結果を非正規化されたビュー モデル テーブルにマージすることにより、基本的にビュー モデルを最新の状態に保つ状況を持っています。

SQL 2008 にアップグレードしたので、これらを SQL MERGE ステートメントで更新する絶好の機会だと思いました。ただし、クエリを記述した後、MERGE ステートメントのサブツリー コストは 1214.54 です。従来の方法では、挿入/更新/削除の合計はわずか 0.104 でした。

同じ正確な操作を説明するより簡単な方法が、どうしてこれほどくだらないものになるのか、私にはわかりません。おそらく、私ができない私のやり方の誤りを見ることができるでしょう。

テーブルに関するいくつかの統計: 190 万行あり、MERGE 操作ごとに 100 以上の行が挿入、更新、または削除されます。私のテスト ケースでは、影響を受けるのは 1 つだけです。

-- This table variable has the EXACT same structure as the published table
-- Yes, I've tried a temp table instead of a table variable, and it makes no difference
declare @tSource table
(
    Key1 uniqueidentifier NOT NULL,
    Key2 int NOT NULL,
    Data1 datetime NOT NULL,
    Data2 datetime,
    Data3 varchar(255) NOT NULL, 
    PRIMARY KEY 
    (
        Key1, 
        Key2
    )
)

-- Fill the temp table with the desired current state of the view model, for
-- only those rows affected by @Key1.  I'm not really concerned about the
-- performance of this.  The result of this; it's already good.  This results
-- in very few rows in the table var, in fact, only 1 in my test case
insert into @tSource
select *
from vw_Source_View with (nolock)
where Key1 = @Key1

-- Now it's time to merge @tSource into TargetTable

;MERGE TargetTable as T
USING tSource S
    on S.Key1 = T.Key1 and S.Key2 = T.Key2

-- Only update if the Data columns do not match
WHEN MATCHED AND T.Data1 <> S.Data1 OR T.Data2 <> S.Data2 OR T.Data3 <> S.Data3 THEN
    UPDATE SET
        T.Data1 = S.Data1,
        T.Data2 = S.Data2,
        T.Data3 = S.Data3

-- Insert when missing in the target
WHEN NOT MATCHED BY TARGET THEN
    INSERT (Key1, Key2, Data1, Data2, Data3)
    VALUES (Key1, Key2, Data1, Data2, Data3)

-- Delete when missing in the source, being careful not to delete the REST
-- of the table by applying the T.Key1 = @id condition
WHEN NOT MATCHED BY SOURCE AND T.Key1 = @id THEN
    DELETE
;

では、これはどのようにして 1200 のサブツリー コストになるのでしょうか? テーブル自体からのデータ アクセスは非常に効率的です。実際、MERGE のコストの 87% は、チェーンの終わり近くにあるソート操作からのもののようです。

MERGE (0%) <- インデックスの更新 (12%) <- 並べ替え (87%) <- (...)

そして、そのソートには、それに出入りする0行があります。0 行をソートするのに 87% のリソースが必要なのはなぜですか?

アップデート

MERGE 操作のみの実際の (推定ではない)実行計画をGist に投稿しました。

4

1 に答える 1

2

サブツリーのコストは、大雑把に考える必要があります (カーディナリティ エラーが大きい場合は特にそうです)。SET STATISTICS IO ON; SET STATISTICS TIME ON;出力は、実際のパフォーマンスのより良い指標です。

ゼロ行の並べ替えは、リソースの 87% を使用しません。あなたの計画におけるこの問題は、統計の見積もりの​​ 1 つです。実際の計画に示されているコストは、依然として推定コストです。実際に起こったことを考慮してそれらを調整することはありません。

計画には、フィルタによって 1,911,721 行が 0 に削減されるポイントがありますが、今後の推定行数は 1,860,310 です。その後、すべてのコストは偽物であり、87% のコストと推定される 3,348,560 行のソートで最高潮に達します。

カーディナリティ推定エラーは、同等の述語を持つMergeの推定計画を調べることで、ステートメントの外部で再現できますFull Outer Join(同じ 1,860,310 行の推定値が得られます)。

SELECT * 
FROM TargetTable T
FULL OUTER JOIN  @tSource S
    ON S.Key1 = T.Key1 and S.Key2 = T.Key2
WHERE 
CASE WHEN S.Key1 IS NOT NULL 
     /*Matched by Source*/
     THEN CASE WHEN T.Key1 IS NOT NULL  
               /*Matched by Target*/
               THEN CASE WHEN  [T].[Data1]<>S.[Data1] OR 
                               [T].[Data2]<>S.[Data2] OR 
                               [T].[Data3]<>S.[Data3]
                         THEN (1) 
                     END 
                /*Not Matched by Target*/     
                ELSE (4) 
           END 
       /*Not Matched by Source*/     
      ELSE CASE WHEN  [T].[Key1]=@id 
                THEN (3) 
            END 
END IS NOT NULL

とはいえ、フィルター自体までの計画は非常に最適ではないように見えます. 2 つのクラスター化インデックス レンジ シークを含むプランが必要な場合に、完全なクラスター化インデックス スキャンを実行しています。1 つはソースの結合から主キーに一致する単一の行を取得するためのもので、もう 1 つはT.Key1 = @id範囲を取得するためのものです (これは、後でクラスター化されたキーの順序に並べ替える必要がないようにするためでしょうか?)

原案

おそらく、この書き直しを試して、うまくいくか悪いかを確認してください。

;WITH FilteredTarget AS
(
SELECT T.*
FROM TargetTable  AS T WITH (FORCESEEK)
JOIN @tSource S
    ON (T.Key1 = S.Key1
    AND S.Key2 = T.Key2)
    OR T.Key1 = @id
)
MERGE FilteredTarget AS T
USING @tSource S
ON (T.Key1 = S.Key1
   AND S.Key2 = T.Key2)


-- Only update if the Data columns do not match
WHEN MATCHED AND S.Key1 = T.Key1 AND S.Key2 = T.Key2 AND 
                                         (T.Data1 <> S.Data1 OR
                                          T.Data2 <> S.Data2 OR 
                                          T.Data3 <> S.Data3) THEN
  UPDATE SET T.Data1 = S.Data1,
             T.Data2 = S.Data2,
             T.Data3 = S.Data3

-- Note from original poster: This extra "safety clause" turned out not to
-- affect the behavior or the execution plan, so I removed it and it works
-- just as well without, but if you find yourself in a similar situation
-- you might want to give it a try.
-- WHEN MATCHED AND (S.Key1 <> T.Key1 OR S.Key2 <> T.Key2) AND T.Key1 = @id THEN
--   DELETE

-- Insert when missing in the target
WHEN NOT MATCHED BY TARGET THEN
    INSERT (Key1, Key2, Data1, Data2, Data3)
    VALUES (Key1, Key2, Data1, Data2, Data3)

WHEN NOT MATCHED BY SOURCE AND T.Key1 = @id THEN
    DELETE;
于 2011-09-15T01:04:00.127 に答える