データベース: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が問題ではないと思いますか?
いくつかの追加のインデックス作成戦略を提供するために、ここに私が思いついたものがあります。
テーブル内のデータ分布の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()
に対してクエリを実行しているため、インデックス内のMessageId
列DESC
を注文する方が理にかなっている可能性があります。これにより、計画で不要なSort
を回避できる場合があります。
_CREATE INDEX ix_love_and_rockets
ON dbo.Whatever
(
MessagePlatform,
MessageId DESC,
MessageCategory );
_
2番目の戦略:WindowsおよびLinuxのMessagePlatform
sを最も重視する場合、これらの値のフィルター選択されたインデックスが意味をなす場合があります。 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多い行です。
お役に立てれば!
必要なすべての列がインデックス内にあり、テーブルをスキャンするより高速であるため、現在インデックススキャンを取得しています。ただし、インデックスの最初の列が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
使用するインデックスは、MessageCategory
がWHERE
ステートメントにあるかどうかによって異なります。
LinuxレコードはWindowsレコードよりも多くあります(1,762,461対11,786)。現在のインデックスの状態では、インデックススキャンは最大のMessageID
から始まり、一致するMessagePlatform
が見つかるまでリストを下に移動します。 Linuxのレコードはもっとたくさんあるので、かなり早くヒットするでしょう。 Windowsの方がはるかに少ないので、さらにスキャンする必要があり、時間がかかります。