web-dev-qa-db-ja.com

リンクサーバーからデータを照会し、フィルターするパラメーターを渡すにはどうすればよいですか?

複数のデータベースで実行する必要がある非常に大きなクエリがあり、その結果が一時テーブルに追加されて返されます。

基本的な構文は次のようになります。

INSERT INTO #tmpTable (Id, ...)

SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id

INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id

個々のサーバーでローカルに実行すると、クエリは高速に実行されますが、上記のような4つの部分からなる名前を使用してリンクサーバーから実行すると、実行に長い時間がかかります。

問題は、最初にフィルター処理されていない結果セットについてリンクサーバーにクエリを実行し、次にローカルサーバーの#tmpIdsテーブルに結合するため、クエリの実行に非常に長い時間がかかるようです。

次のように、リンクサーバーで結果セットをフィルター処理するためにIDをハードコーディングした場合

SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
-- INNER JOIN #tmpIds as T ON T1.Id = T.Id
INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id

WHERE T1.Id IN (1, 2, 3)

ほんの数秒ですばやく実行されます。

このクエリを実行して、リンクされたサーバーからのクエリの結果セットを#tmpIdテーブルでフィルタリングしてから、結果セットをローカルサーバーに返す方法はありますか?

注意すべきいくつかの事柄

  • クエリは非常に大きく複雑であり、Dynamic SQLはメンテナンスの悪夢の原因となるため、実行可能なオプションではありません。

    リンクされたサーバー上でそれを行う方法がある場合、ストアドプロシージャやUDFを実行するなど、動的SQLを他の目的で使用する方法についての提案を受け入れます(sp_executeSQLなどのいくつかの異なるメソッドを試してみました、 OPENROWSET、およびOPENQUERYですが、すべて失敗しました)。

  • 4部構成の命名規則を使用しているため、リモートサーバーでUDFを使用できません
  • 分散トランザクションが無効になっているため、以下は機能しません

    INSERT INTO #table 
    EXEC Server.Database.dbo.StoredProcedure @ids
    
2
Rachel

パフォーマンスの問題は実際にはLEFT OUTER JOINテーブルに関係しています。それらをINNER JOINに変更した場合、またはSELECT列からそれらのデータを除外した場合、クエリは正常に実行されました。

結局、リンクサーバーでViewを作成し、そこから必要なすべてのデータを取得して、プライマリサーバーから#tmpIdsテーブルに単純に結合しました。

すべてを結合し、フィルタリングする前に2番目のサーバーにプルダウンすることは今行っているものと同じであり、同じパフォーマンスの問題につながると思っていたので、これがうまくいくとは思いませんでしたが、驚くべきことに、事実である。

CREATE VIEW MyView 
AS

SELECT T1.Id, T2.ColA, ...
FROM Table1 as T1
INNER JOIN Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Table6 as T6 ON T5.Id = T6.Id

GO

そして

INSERT INTO #tmpTable (Id, ...)

SELECT T1.Id, T1.ColA, ...
FROM Server.Database.dbo.MyView as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id

結合された列はすべて正しく索引付けされましたが、 この答え によると

リモートサーバー上のテーブルにインデックスが存在する場合でも、SQLはインデックスを利用するローカルクエリプランを構築できる一方で、それらを利用できない場合があります。

そして これ

リンクサーバーにできるだけ多くの処理を実行させます。
SQL Serverがリンクサーバー上でクエリを最適化することは不可能です。

そのため、クエリに使用されるクエリプランは定義されたインデックスを使用しておらず、SQL ServerはLEFT OUTER JOINテーブルに対して不適切なクエリプランを生成していたと思います。

2
Rachel

FORCE ORDERクエリヒントを試しましたか?最適化時に、クエリにリストされているとおりに結合の順序をコンパイラに保持させます。

SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id

INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id
OPTION (FORCE ORDER)

編集: FORCE ORDERが機能しない場合、次のようなことを考えましたか?

WHERE T1.Id IN (SELECT Id FROM #tmpIds)

2番目の編集:もう一度試してください。これは少し複雑ですが。

あなたはこのようなことをすることができます:

リモートサーバーで永続的な「一時」テーブルを作成する

CREATE TABLE tmpTable1 (Id INT)

次に(まだリモートサーバー上で)ビューを作成します

CREATE VIEW queryView AS
SELECT Table1.* 
FROM Table1
JOIN tmpTable1
    ON Table1.Id = tmpTable1.Id

次に、「ホーム」インスタンスのプロセスで

DELETE FROM Server.Database.dbo.tmpTable1
INSERT INTO Server.Database.dbo.tmpTable1 VALUES
SELECT * FROM #tmpIds

次に、クエリでServer.Database.dbo.queryViewに結合します

0
Kenneth Fisher

各リンクサーバーでユーザー定義関数を作成し、それらから必要なすべてのデータを取得して、次のようにopenqueryで関数をクエリすることをお勧めします。

INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, ...
FROM OPENQUERY([Server], 'SELECT * FROM Database.dbo.UdfGetData()')
INNER JOIN #tmpIds as T ...

このようにして、インポートする必要があるすべてのデータがリンクサーバーで処理され、結果はopequery経由でのみ取得されます。

次のように各テーブルをクエリすると:

... INNER JOIN Server.Database.dbo.Table2 ...

リンクサーバーの各テーブルからローカルサーバーにすべてのデータを取得し、メモリに読み込みます。次に、ローカルサーバーで結合が実行されます。おそらく(私が推測しているように)、すべてのインデックスがありません。したがって、必要以上のデータをインポートしているだけでなく、インデックスがないため、結合も遅くなります。

少し前にこの問題があり、OPENQUERYを使用すると、プロセスの実行時間を約2日間(実際にはサーバーが遅くなるまで誰も気付かなかった)から10分に短縮できました。

このメソッドの欠点は、openqueryのクエリ文字列のパラメーターを連結する必要があることです。これを克服するには、次のことをお勧めします。

CREATE TABLE #TempT (
    a INT NOT NULL,
    b ...
);

DECLARE @query VARCHAR(MAX);
SET @query
    = 'SELECT a, b, ... FROM OPENQUERY([Server], SELECT * FROM Database.dbo.UdfGetData(' + @p1 + ',' + @p2 ')';
-- @p1 and @p2 are the parameters, but you will need to format them according to the datatype: DATETIME, VARCHAR, etc

INSERT INTO #TempT EXEC (@query);

INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, ...
FROM #TempT
    INNER JOIN #tmpIds as T ...

私はそれがきれいではないことを知っています、そしてそれは '悪い考えの不吉な組み合わせ' であると言う人もいますが、それはXDの働きをします