web-dev-qa-db-ja.com

まったく同じクエリ-異なるパフォーマンス

データベース:SQL Server 12.0.5207

フィルター用語の1つの値を除いて、すべての点でまったく同じクエリがいくつかあります。同じテーブル(別のサーバーのスキーマのコピーではない)。したがって、同じインデックス、リソースなど。すべてが完全に同一です。

このクエリは1秒未満で実行されます。

SELECT 
         MAX(MessageID) AS [MaxID]
FROM BoothComm.UniversalMessageQueue
WHERE 
       MessagePlatform = 'linux'

このクエリは1秒未満で実行されます。

SELECT 
         MAX(MessageID) AS [MaxID]
FROM BoothComm.UniversalMessageQueue
WHERE 
       MessagePlatform = 'linux'
       AND
       MessageCategory = 'accounting'

このクエリは1秒未満で実行されます。

SELECT 
         MAX(MessageID) AS [MaxID]
FROM BoothComm.UniversalMessageQueue
WHERE 
       MessagePlatform = 'windows'

それで、なぜこれは実行に30秒近くかかるのですか?

SELECT 
         MAX(MessageID) AS [MaxID]
FROM BoothComm.UniversalMessageQueue
WHERE 
       MessagePlatform = 'windows'
       AND
       MessageCategory = 'accounting'

私の同僚は、テーブルに別のインデックスを追加して、レイテンシのbusiness問題を解決しました。このインデックスは、他のクエリを瞬時に高速化しながら、30秒を1秒に短縮しました。繰り返しますが、 実行計画はまったく同じです

(インデックススキャンは100%である必要があります)。私は他のフォーラムからアドバイスを受け、クエリの列の順序がインデックスに格納されている順序と一致することを確認しました...

CREATE NONCLUSTERED INDEX [MessageID and Platform and Category] ON [BoothComm].[UniversalMessageQueue]
(
    [MessageID] ASC,
    [MessagePlatform] ASC,
    [MessageCategory] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

役立つ場合に備えて、テーブルスキーマも提供しています。

CREATE TABLE [BoothComm].[UniversalMessageQueue](
    [MessageQueueId] [bigint] IDENTITY(1,1) NOT NULL,
    [MessageID] [bigint] NOT NULL,
    [MessagePlatform] [nvarchar](50) NULL,
    [AssetNumber] [nvarchar](50) NOT NULL,
    [MessageState] [int] NULL,
    [MessageStateLabel] [nvarchar](50) NULL,
    [MessageType] [int] NULL,
    [MessageTypeLabel] [nvarchar](50) NULL,
    [MessageCategory] [nvarchar](50) NULL,
    [MessageSource] [int] NULL,
    [MessageSourceLabel] [nvarchar](50) NULL,
    [MessageSourceSerialNumber] [nvarchar](50) NULL,
    [MessageCreateDate] [datetime] NULL,
    [MessageTransmitDate] [datetime] NULL,
    [MessageReceivedDate] [datetime] NULL,
    [MessageStoredDate] [datetime] NULL,
    [XMLPayload] [nvarchar](max) NULL,
    [JSONPayload] [nvarchar](max) NULL,
    [SemanticXML] [nvarchar](max) NULL,
    [SemanticJSON] [nvarchar](max) NULL,
    [MessageSequenceNumber] [int] NULL,
    [ERPImportDate] [datetime] NULL,
    [ERPImportStatus] [int] NULL,
    [ERPMsg] [nvarchar](max) NULL,
    [NormalizationDate] [datetime] NULL,
    [NormalizationStatus] [int] NULL,
    [NormalizationDesc] [nvarchar](max) NULL,
    [SemanticDate] [datetime] NULL,
    [SemanticStatus] [int] NULL,
    [SemanticDesc] [nvarchar](max) NULL,
    [CreatedDate] [datetime] NOT NULL DEFAULT (getdate()),
    [CreatedBy] [nvarchar](50) NOT NULL DEFAULT ('ETL'),
    [UpdatedDate] [datetime] NOT NULL DEFAULT (getdate()),
    [UpdatedBy] [nvarchar](50) NOT NULL DEFAULT ('ETL'),
    [ETL_ID] [uniqueidentifier] NULL,
PRIMARY KEY CLUSTERED 
(
    [MessageQueueId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [CK_ETL_Unique_MessageID_Platform] UNIQUE NONCLUSTERED 
(
    [MessageID] ASC,
    [MessagePlatform] ASC,
    [MessageType] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

これにより、問題を再現するのに十分なコードが提供されます。表に約1,100万件のレコードを入力するだけで、問題を確認できます。


数回表示され、確認することすら考えていなかったため、「windows」レコードと「linux」レコードの数を調べました。

SELECT COUNT(*) 
FROM BoothComm.UniversalMessageQueue 
WHERE 
    MessageCategory = 'Accounting'
    AND
    MessagePlatform = 'linux';
-- returned 1762461

SELECT COUNT(*) 
FROM BoothComm.UniversalMessageQueue 
WHERE 
    MessageCategory = 'Accounting'
    AND
    MessagePlatform = 'windows';
-- returned 11786

だから...私はrecordcountが問題ではないと思いますか?

4

いくつかの追加のインデックス作成戦略を提供するために、ここに私が思いついたものがあります。

テーブル内のデータ分布のrestについてはわかりません。コンボとしての_Accounting/Linux_は1,762,461行を構成し、_Accounting/Windows_は11,786行を構成することを知っています。他の部門やプラットフォームがあると想定して、このデータを表にまとめました。

_USE tempdb;

CREATE TABLE dbo.Whatever
(
    Id INT IDENTITY(1,1),
    MessageId INT NOT NULL,
    MessageCategory NVARCHAR(50),
    MessagePlatform NVARCHAR(50)
);

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1762461 x.n, 'Accounting', 'Linux'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 11786 x.n, 'Accounting', 'Windows'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x


INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 5000000 x.n, 'HR', 'Unix'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x


INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1000000 x.n, 'Accounting', 'Mac'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1000000 x.n, 'IT', 'Windows'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1000000 x.n, 'IT', 'Linux'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1000000 x.n, 'HR', 'Mac'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

INSERT dbo.Whatever ( MessageId, MessageCategory, MessagePlatform )
SELECT TOP 1000000 x.n, 'HR', 'Windows'
FROM (SELECT ROW_NUMBER() OVER (ORDER BY @@ROWCOUNT) AS n
        FROM sys.messages AS m
        CROSS JOIN sys.messages AS m2) AS x

ALTER TABLE dbo.Whatever ADD CONSTRAINT pk_thoughtful PRIMARY KEY CLUSTERED (Id)
_

最初の戦略:MAX()に対してクエリを実行しているため、インデックス内のMessageIdDESCを注文する方が理にかなっている可能性があります。これにより、計画で不要なSortを回避できる場合があります。

_CREATE INDEX ix_love_and_rockets
    ON dbo.Whatever
(
    MessagePlatform,
    MessageId DESC,
    MessageCategory );
_

2番目の戦略:WindowsおよびLinuxのMessagePlatformsを最も重視する場合、これらの値のフィルター選択されたインデックスが意味をなす場合があります。 MessageIdの降順を使用します。

_CREATE INDEX ix_bauhaus_was_better
    ON dbo.Whatever
(
    MessageCategory,
    MessageId DESC )
    INCLUDE ( MessagePlatform )
    WHERE MessagePlatform IN ( 'Windows', 'Linux' );
_

結果:いくつかのクエリ例を実行すると、 インデックスの使用法が混在 になります。

_SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w
WHERE  w.MessagePlatform = 'Linux'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w
WHERE  w.MessagePlatform = 'Windows'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w
WHERE  w.MessagePlatform = 'Windows'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w
WHERE  w.MessagePlatform = 'Linux'
       AND 1 = ( SELECT 1 );
_

統計時間とI/Oの結果は次のとおりです。

  • クエリ1

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「何でも」。スキャンカウント1、論理読み取り3、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。

  • クエリ2

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「何でも」。スキャンカウント1、論理読み取り10938、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

    SQL Server実行時間:CPU時間= 625ミリ秒、経過時間= 614ミリ秒。

  • クエリ3

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「何でも」。スキャンカウント1、論理読み取り5、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。

  • クエリ4

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「何でも」。スキャンカウント1、論理読み取り4、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。

フィルターされたインデックスと降順のインデックスの両方について少しブログを作成しました ここ 、もう少し情報が必要な場合。

クエリ2に時間がかかるのはなぜですか?その答えは「アルファベット」です。

MessagePlatformの順序でDESCを使用してインデックスを作成すると、順序が逆になります。

次の2つのインデックスのみを検討してください。

_CREATE INDEX ix_love_and_rockets
    ON dbo.Whatever
(
    MessagePlatform,
    MessageId DESC,
    MessageCategory );


CREATE INDEX ix_rockets_and_love
    ON dbo.Whatever
(
    MessagePlatform DESC,
    MessageId DESC,
    MessageCategory );
_

MessagePlatformの順序を反転しています。これらのインデックスを示唆する同じ2つのクエリを実行しても、パフォーマンスの違いは反転しません。

_SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w WITH (INDEX = ix_love_and_rockets)
WHERE  w.MessagePlatform = 'Linux'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w WITH (INDEX = ix_love_and_rockets)
WHERE  w.MessagePlatform = 'Windows'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w WITH (INDEX = ix_rockets_and_love)
WHERE  w.MessagePlatform = 'Linux'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );

SELECT MAX(w.MessageId) AS MaxId
FROM   dbo.Whatever AS w WITH (INDEX = ix_rockets_and_love)
WHERE  w.MessagePlatform = 'Windows'
       AND w.MessageCategory = 'Accounting'
       AND 1 = ( SELECT 1 );
_
  • クエリ1

    テーブル「何でも」。スキャンカウント1、論理読み取り4、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。

  • クエリ2

    テーブル「何でも」。スキャンカウント1、論理読み取り9338、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

    SQL Server実行時間:CPU時間= 687 ms、経過時間= 684 ms。

  • クエリ3

    テーブル「何でも」。スキャンカウント1、論理読み取り5、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。

  • クエリ4

    SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「何でも」。スキャンカウント1、論理読み取り9337、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

    SQL Server実行時間:CPU時間= 672ミリ秒、経過時間= 678ミリ秒。

根本的な原因:どちらのクエリでも、MessagePlatformを探しますが、MessageCategoryに残余述語があります。

違いはデータの分布にあります。私のテストデータでは、Windowsは、MessageCategoryよりもLinuxより多くの行をフィルターで除外します。具体的には、_Windows/HR_コンボに対して1mm多い行です。

お役に立てれば!

8
Erik Darling

必要なすべての列がインデックス内にあり、テーブルをスキャンするより高速であるため、現在インデックススキャンを取得しています。ただし、インデックスの最初の列がWHEREステートメントになく、戻りを制限しないため、シークではなくスキャンする必要があります。インデックス列は常にMessagePlatformステートメント内にあるため、WHEREが最初になるようにインデックス列を再配置する必要があります。

データのサイズと必要な挿入速度に応じて、2つのインデックスを検討することをお勧めします。インデックスが1つだけ必要な場合は、次のようにします。

CREATE NONCLUSTERED INDEX [MessageID and Platform and Category] 
ON [BoothComm].[UniversalMessageQueue]
(
    [MessagePlatform] ASC,
    [MessageID] ASC,
    [MessageCategory] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
 DROP_EXISTING = OFF, ONLINE = OFF, 
 ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

しかし、あなたが2つを買う余裕があるなら、私は移動します:

CREATE NONCLUSTERED INDEX [MessageID and Platform] 
ON [BoothComm].[UniversalMessageQueue]
(
    [MessagePlatform] ASC,
    [MessageID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
 DROP_EXISTING = OFF, ONLINE = OFF, 
 ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [MessageID and Platform and Category] 
ON [BoothComm].[UniversalMessageQueue]
(
    [MessagePlatform] ASC
    [MessageCategory] ASC,
    [MessageID] ASC,
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
 DROP_EXISTING = OFF, ONLINE = OFF, 
 ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

使用するインデックスは、MessageCategoryWHEREステートメントにあるかどうかによって異なります。


LinuxレコードはWindowsレコードよりも多くあります(1,762,461対11,786)。現在のインデックスの状態では、インデックススキャンは最大のMessageIDから始まり、一致するMessagePlatformが見つかるまでリストを下に移動します。 Linuxのレコードはもっとたくさんあるので、かなり早くヒットするでしょう。 Windowsの方がはるかに少ないので、さらにスキャンする必要があり、時間がかかります。

8
indiri