web-dev-qa-db-ja.com

このCTEが結果の大部分をすぐに返すのに、完了するまでに数分かかるのはなぜですか?

私はおおよそ次のようなスキーマを持っています(これは実際のスキーマを単純化したものです):

CREATE TABLE foo (
    key1 NUMERIC(6) NOT NULL,
    key2 VARCHAR(32) NOT NULL,
    val VARCHAR(255) NULL,
    CONSTRAINT foo_pk PRIMARY KEY (key1, key2) --PK on key1, key2
    )
GO
CREATE TABLE bar (
    key1 NUMERIC(6) NOT NULL,
    key2 VARCHAR(32) NOT NULL,
    val VARCHAR(255) NULL,
    CONSTRAINT bar_pk PRIMARY KEY (key1, key2) --PK on key1, key2
    )
GO
CREATE TABLE aliases (
    id VARCHAR(32) NOT NULL PRIMARY KEY,
    text VARCHAR(255) NOT NULL,
    CONSTRAINT aliases_uk UNIQUE (text) --PK on id, unique constraint on text
    )
GO

INSERT INTO aliases (id, text)
SELECT REPLACE(NEWID(), '-', ''), 'Text1' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text2' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text3' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text4' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text5' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text6' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text7' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text8' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text9' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text10' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text11' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text12' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text13' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text14' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text15' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text16';
GO

BEGIN
    DECLARE @i INT = 1;
    WHILE (@i <= 234)
    BEGIN
        INSERT INTO foo (key1, key2, val) SELECT @i, id, id FROM aliases;
        SET @i = @i + 1;
    END;
    INSERT INTO bar (key1, key2, val) SELECT key1 + 234, key2, val FROM foo;
END;
GO

すべてのkey1key2のペアのリストを取得したい(key2のわかりやすい名前を使用し、valsのわかりやすいエイリアスを連結したもの。私の最初の試みは次のようになります:

WITH
foos_and_bars (key1, key2, val) AS (
    SELECT key1, key2, val FROM foo UNION ALL
    SELECT key1, key2, val FROM bar),
texts (key1, key2_name, val_text, rnum) AS (
    SELECT key1, a1.text, a2.text, ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY val)
    FROM foos_and_bars
    JOIN aliases a1 ON a1.id = foos_and_bars.key2
    LEFT JOIN aliases a2 ON a2.id = foos_and_bars.val),
partitioned (key1, key2_name, val_text, rnum, maxnum) AS (
    SELECT key1, key2_name, val_text, rnum, MAX(rnum) OVER (PARTITION BY key1, key2_name)
    FROM texts),
recurse (key1, key2_name, val_texts, rnum, maxnum) AS (
    SELECT key1, key2_name, val_text, rnum, maxnum
    FROM partitioned
    WHERE rnum = 1
    UNION ALL
    SELECT r.key1,
        r.key2_name, 
        CAST(r.val_texts + CHAR(13) + CHAR(10) + p.val_text AS VARCHAR(255)),
        p.rnum,
        r.maxnum
    FROM recurse r
    JOIN partitioned p
    ON p.key1 = r.key1
    AND p.key2_name = r.key2_name
    AND p.rnum = r.rnum + 1)
SELECT * FROM recurse WHERE rnum = maxnum;

それは機能し、1秒未満で7480行を取得しますが、SSMSは2分40秒以上実行され、最後の8行を混乱させます。これらの最後の行は最初の行よりも複雑ではなく(実際、現在システムにあるデータでは、rnummaxnumは1より大きくなることはありません)、これらのテーブルはこのSEの質問をあざけって、ロックはないはずです。

上から下にpartitionedを選択すると、1秒未満で7488行が生成されるため、問題は再帰的なCTEに限定されているようです。 CTEは何に行き詰まっているのでしょうか?

実行計画(簡略化されたスキーマに一致する設定を使用):

出力は、レコードの検索に使用されるSybaseウィンドウに戻るビューに送られます。そのため、プログラムはデフォルトで一度に1000行をプルしますが、すべての行を取得できる必要があります。

5
JAF

再帰的SQLクエリのアンカー部分は、7488行すべてを生成します。私のマシンでは、クエリのその部分は100ミリ秒未満で終了します。 SSMSは、グリッド結果の7488行すべてをすぐには表示しません。私にも7480しか表示されません。結果はパケットで送信され、残りの8行ではパケットを満たすのに十分ではないため、これが発生したと思います。

クエリの後半は、前半に比べて非常にコストがかかります。実行プランを見ると、ネストされたループの内側に6つのクラスター化インデックススキャンが見られます。これらのスキャンは、アンカーパーツのすべての行に対して少なくとも1回実行されます。 SQL Serverは、数分にわたって合計44928スキャンを実行し、最終的にその部分から0行を生成します。実行のその部分が完了すると、SQL Serverは残りの行のパケットをクライアントに送信し、完了したクエリの結果グリッドに7488行すべてが表示されます。最後の8つの8行を見つけるのに3分かかるとは限りません。ほぼ瞬時に見つかります。送信にはわずか3分かかります。

実際のプランを確認すると、クエリの下半分で実行された大量の作業を確認できます。矢印の太さとスキャンオペレーターの実行回数に注意してください。

actual plan

クエリが遅いのはそのためです。このクエリで何を達成しようとしているのか理解できないので、速くするためのアドバイスはありません。

6
Joe Obbish