クエリを最適化する方法


9

私はこれに似たデータベース構造を持っています、

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

DispatchLinkテーブルのポイントは、2つのDispatchレコードをリンクすることです。ちなみに、レガシーのためにディスパッチテーブルで複合主キーを使用しているので、多くの苦労なしにそれを変更することはできません。また、リンクテーブルはそれを行う正しい方法ではないかもしれませんか?しかし、再びレガシー。

だから私の質問、このクエリを実行すると

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

DispatchLinkテーブルでインデックスシークを実行することはできません。常にフルインデックススキャンを実行します。少数のレコードで問題ありませんが、そのテーブルに50000がある場合、クエリプランに従ってインデックス内の50000レコードをスキャンします。これは、結合句に「ands」と「or」が含まれているためですが、SQLが代わりに2つのインデックスシークを実行できない理由を頭で理解できません。1つは「or」の左側です。 1つは「or」の右側にあります。

これについての説明をお願いします。クエリを調整せずに実行できない限り、クエリを高速化するための提案ではありません。その理由は、上記のクエリをマージレプリケーション結合フィルターとして使用しているため、残念ながら別の種類のクエリを追加することはできないためです。

更新:たとえば、これらは私が追加しているインデックスのタイプです、

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

したがって、インデックスを使用しますが、インデックス全体でインデックススキャンを実行するため、50000レコードはインデックス内の50000レコードをスキャンします。


DispatchLinkテーブルにインデックスはありますか?
ypercubeᵀᴹ

上記で試したインデックスを追加しました。
ピーター2012

クエリ:「select * from Dispatch d inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1 or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2」削除しようとする「OR」条件を使用して、それぞれ「OR」を使用しない2つのSELECTステートメントのUNIONで置き換えます。また、テストをできるだけ純粋にするために、両方のSELECTで「*」ではなく唯一のキー列を使用します。
NoChance 2012

SQL Kiwiに感謝します。これは以前試したものですが、残念ながら機能しませんでした。
ピーター2012

1
レプリケーションをより簡単なクエリで発行できますか?select * from Dispatch d inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1はいの場合、DispatchLinkでデータを複製して結果を引き続き有効にすることができます...
AK

回答:


12

オプティマイザーは多くの代替案(複数のシークを含むものを含む)を考慮することができますが、選言(OR述語)の場合、デフォルトではインデックスの交差を含む計画は考慮されません。与えられたインデックス:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

インデックスシークを強制できます(SQL Server 2008以降を想定)。

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

FORCESEEKプラン

あなたのサンプルデータを使用して、で、計画原価求める0.0332551と比較して単位0.0068057スキャン計画のために:

スキャン計画

私たちが試すことができるあらゆる種類のクエリの書き換えとヒントがあります。オプティマイザが元の計画で考慮しないオプションを昇格させるための書き換えの一例は、次のとおりです。

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

この実行プランは、最初のインデックスで一致が見つかった場合、2番目のインデックスを検索しません。

APPLY TOPプラン

これは、デフォルトのFORCESEEK計画よりもわずかに優れたパフォーマンスを発揮する場合があります。

新しいインデックスを追加せずに、Dispatchテーブルに強制的にシークすることもできます。

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

シーク2

これは、各テーブルの行数などによって、最初の例よりも良い場合と悪い場合があります。APPLY + TOP改善はまだ可能です。

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

これは非常に役立つ回答です。私は別の質問をしましたdba.stackexchange.com/questions/23773/analysing-a-query-planは、実際のデータ(テストデータではない)の実際のクエリプランを示しています。クエリプランのボトルネックを正確に理解する知識がありません。おそらくあなたは見てみることができますか?
ピーター2012

「FORCESEEK」を追加すると、クエリが10分以上かかるのではなく、9秒で実行されるため、これは本当に興味深いものです。統計を更新しても違いはありません。なぜクエリアナライザーはそれをそれほど間違っているのでしょうか?
ピーター2012

デザインは正しいと思います。列の繰り返しについてどういう意味ですか?2つのDispatchレコードを関連するものとしてリンクする必要があるテーブル構造をどのように設計しますか?「実際の」テーブルにはそれ自体の主キーフィールドがあることを明確にするために、そうですが、Dispatchに複合キーがあることは正確には役に立ちません。
ピーター2012

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