IN()を使用してクエリのパフォーマンスを改善する


14

次のSQLクエリがあります。

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

またEvent、列のテーブルにインデックスがありますTimeStamp。私の理解では、このインデックスはIN()ステートメントのために使用されていません。だから私の質問は、この特定のインデックスを作成する方法がありますIN()ステートメントの作成してこのクエリを高速化ますか?

またEvent.EventTypeID IN (2, 5, 7, 8, 9, 14)、インデックスのフィルターとしてを追加しようとしましたTimeStampが、実行プランを見ると、このインデックスを使用しているようには見えません。これに関する提案や洞察は大歓迎です。

以下はグラフィカルなプランです。

実行計画

そして、これは.sqlplanファイルへのリンクです


実行計画も見ることができますか?:)
dezso

1
.sqlplan拡張子を付けて、実際の実行計画(推定ではない)を投稿してください。ほとんどの人は、グラフィカルな計画のスクリーンショットを投稿したいだけで、それはあまり役に立ちません。
アーロンバートランド

OK実行計画を追加し、SQLクエリを更新しました。
サンダーススカイ

@SandersKY .sqlplanファイルをインライン化して、質問に関連するすべてのものを同じサイトに保管するのが最善です。
トリグヴェLaugstøl

1
@trygvis-投稿の長さの制限のために、それはしばしば不可能です。恥スタック交換は、投稿の添付ファイルを内部でホストすることをサポートしていません。
マーティンスミス

回答:


18

次の一般的な形式の表が与えられます。

CREATE TABLE Device 
(
    ID integer PRIMARY KEY
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(50) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    [TimeStamp] datetime NOT NULL, 
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device
);

次のインデックスが役立ちます。

CREATE INDEX f1 
ON [Event] ([TimeStamp], EventTypeID) 
INCLUDE (DeviceID)
WHERE EventTypeID IN (2, 5, 7, 8, 9, 14);

クエリの場合:

SELECT
  [Event].ID,
  [Event].[TimeStamp],
  EventType.Name,
  Device.ID
FROM
  [Event]
INNER JOIN EventType ON EventType.ID = [Event].EventTypeID
INNER JOIN Device ON Device.ID = [Event].DeviceID
WHERE
  [Event].[TimeStamp] BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.EventTypeID IN (2, 5, 7, 8, 9, 14);

フィルターはAND句の要件を満たします。インデックスの最初のキーにより、[TimeStamp]フィルター処理されEventTypeIDsDeviceID列のシークが可能になり、列を含めるとインデックスがカバーされます(テーブルDeviceIDへの結合に必要なためDevice)。

完成した計画

インデックスの2番目のキー- EventTypeID厳密に必須ではありません(INCLUDEd列にすることもできます)。ここに述べられた理由でそれをキーに含めました。一般に、少なくともINCLUDEフィルター選択されたインデックスWHERE句の列を使用することをお勧めします。


質問の更新されたクエリと実行計画に基づいて、SSMSによって提案されたより一般的なインデックスは、AaronがEventTypeIDs答えで言及しているようにフィルターのリストが静的でない限り、ここでより良い選択である可能性が高いことに同意します:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY,
    Name nvarchar(50) NOT NULL UNIQUE
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(20) NOT NULL UNIQUE,
    [Description] nvarchar(100) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    PLCTimeStamp datetime NOT NULL,
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device,
    IATA varchar(50) NOT NULL,
    Data1 integer NULL,
    Data2 integer NULL,
);

推奨インデックス(適切な場合は一意と宣言してください):

CREATE UNIQUE INDEX uq1
ON [Event]
    (EventTypeID, PLCTimeStamp)
INCLUDE 
    (DeviceID, IATA, Data1, Data2, ID);

実行計画からのカーディナリティー情報(文書化されていない構文、実動システムでは使用しないでください):

UPDATE STATISTICS dbo.Event WITH ROWCOUNT = 4042700, PAGECOUNT = 400000;
UPDATE STATISTICS dbo.EventType WITH ROWCOUNT = 22, PAGECOUNT = 1;
UPDATE STATISTICS dbo.Device WITH ROWCOUNT = 2806, PAGECOUNT = 28;

更新されたクエリ(テーブルのINリストを繰り返すと、EventTypeこの特定のケースでオプティマイザーが役立ちます):

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2,
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND EventType.ID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

推定実行計画:

二次計画

推測された統計を使用しているため、あなたが得る計画はおそらく異なるでしょう。一般的なポイントは、できる限り多くの情報をオプティマイザーに提供し、400万行の[Event]テーブルに効率的なアクセス方法(インデックス)を提供することです。


8

コストの大部分はクラスター化インデックススキャンであり、このテーブルが実際に広い場合、または出力でこれらの列すべてを本当に必要としない限り、SQL Serverはこれが現在のシナリオで最適なパスであり、他に何も変更されていない。関心のある行の範囲を絞り込むために範囲スキャン(CIシークとラベル付けされています)を使用しますが、出力のために、作成したフィルター選択されたインデックスでも、ルックアップまたはCIスキャンのいずれかが必要になりますこの範囲を対象としています。その場合でも、CIスキャンはおそらく最も安価です(または、少なくともSQL Serverがそのように見積もっています)。

実行計画では、このインデックスが有用であることを示しています。

CREATE NONCLUSTERED INDEX ix_EventTypeID_PLCTimeStamp_WithIncludes
  ON [dbo].[Event] ([EventTypeID],[PLCTimeStamp])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

データの偏りにもよりますが、逆の方が良いかもしれません、例えば:

CREATE NONCLUSTERED INDEX ix_PLCTimeStamp_EventTypeID_WithIncludes
  ON [dbo].[Event] ([PLCTimeStamp],[EventTypeID])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

しかし、どちらかが優れているかどうかを確認するために、両方をテストします-これらのインデックスのいずれかと現在のインデックスとの差はわずかである可能性があります(私たちが知るには変数が多すぎます)インデックスには追加のメンテナンスが必要であり、これはDML操作(挿入/更新/削除)に著しく影響する可能性があります。@SQLKiwiで提案されているように、このインデックスにフィルター条件を含めることも検討できます。で、それが頻繁に検索するEventTypeID値のセットである場合のみです。そのセットが時間の経過とともに変化する場合、フィルター選択されたインデックスはこの特定のクエリでのみ役立ちます。

行数が非常に少ないので、現在パフォーマンスがどれほど悪いのか疑問に思う必要がありますか?このクエリは3行を返します(ただし、拒否された行数は表示されません)。テーブル内の行数は?


4

実行計画を実行したときに、SQL Server 2008 R2が実際にインデックスの提案を行ったことを発見しました。この推奨インデックスにより、クエリの実行が約90%速くなります。

提案されたインデックスは次のとおりです。

CREATE NONCLUSTERED INDEX [INDEX_spBagSearch] ON [dbo].[Event] 
(
    [EventTypeID] ASC,
    [PLCTimeStamp] ASC
)
INCLUDE ( [ID],
[DeviceID],
[Data1],
[Data2],
[IATA]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.