導入と背景
簡単なクエリを最適化する必要がありました (以下の例)。何度か書き直したところ、クエリの書き方によって、同じインデックス操作の推定行数が異なることがわかりました。
当初、クエリはクラスター化インデックス スキャンを実行しました。本番環境のテーブルにはバイナリ列が含まれているため、テーブルが非常に大きく (約 100 GB)、テーブル全体のスキャンの実行に時間がかかりすぎます。
質問
同じインデックス操作で推定行数が異なるのはなぜですか (例を示します)。オプティマイザーはここで何をしていますか?
サンプル データベース - SQL Server 2008 R2 を使用しています
動作を示す本番テーブルの非常に単純化されたバージョンを作成しようとしました。
-- CREATE THE SAMPLE TABLES
----------------------------
CREATE TABLE dbo.MasterTable(
MasterId smallint NOT NULL,
Name varchar(5) NOT NULL,
CONSTRAINT PK_MasterTable PRIMARY KEY CLUSTERED (MasterId ASC)
) ON [PRIMARY]
GO
CREATE TABLE dbo.DetailTable(
DetailId bigint IDENTITY(1,1) NOT NULL,
MasterId smallint NOT NULL,
Name nvarchar(50) NOT NULL,
CreateDate datetime NOT NULL,
CONSTRAINT PK_DetailTable PRIMARY KEY CLUSTERED (DetailId ASC)
) ON [PRIMARY]
GO
ALTER TABLE dbo.DetailTable
ADD CONSTRAINT FK1
FOREIGN KEY(MasterId) REFERENCES dbo.MasterTable (MasterId)
GO
CREATE NONCLUSTERED INDEX IX_DetailTable
ON dbo.DetailTable( MasterId ASC, Name ASC )
GO
-- INSERT SOME SAMPLE DATA
----------------------------
SET NOCOUNT ON
GO
-- These are some Codes. In our system we always use these codes to search for "types" of data.
INSERT INTO dbo.MasterTable (MasterId, Name)
VALUES (1, 'N1'), (2, 'N2'), (3, 'N3'), (4, 'N4'), (5, 'N5'), (6, 'N6'), (7, 'N7'), (8, 'N8')
GO
-- ADD ROWS TO THE DETAIL TABLE
-- Takes about 1 minute to run
-- Don't care about the logic, it's just to get a distribution similar to production system
----------------------------
declare @x int = 1
DECLARE @MasterID INT
while (@x <= 400000)
begin
SET @MasterID = ABS(CHECKSUM(NEWID())) % 8 + 1
INSERT INTO dbo.DetailTable(MasterId,Name,CreateDate)
VALUES(
CASE
WHEN @MasterID IN (1, 3, 4) AND @x % 20 != 0 THEN 2
WHEN @MasterID IN (5, 6) AND @x % 20 != 0 THEN 7
WHEN @MasterID = 8 AND @x % 100 != 0 THEN 7
ELSE @MasterID
END,
NEWID(),
DATEADD(DAY, - ABS(CHECKSUM(NEWID())) % 1000, GETDATE())
)
SET @x = @x + 1
end
go
-- DO THE INDEX AND STATISTIC MAINTENANCE
----------------------------
alter index all on dbo.DetailTable reorganize
alter index all on dbo.MasterTable reorganize
update statistics dbo.DetailTable WITH FULLSCAN
update statistics dbo.MasterTable WITH FULLSCAN
go
準備が整ったので、クエリを開始しましょう
最初に統計を見てみましょうRANGE_HI_KEY=8
。489 個の EQ_ROWS があります。
-- CHECK THE STATISTICS
----------------------------
dbcc show_statistics ('dbo.DetailTable', IX_DetailTable)
GO
次に、クエリを実行します。最初のものは、最適化する必要があった元のクエリです。実行時に現在の実行計画を有効にしてください。「インデックス シーク (非クラスター化) [DetailTable].[IX_DetailTable]」操作を見てください。
-- ORIGINAL QUERY
----------------------------
SELECT d.DetailId
FROM dbo.DetailTable d
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
GO
-- FORCESEEK
----------------------------
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
GO
-- Actual: 489, Estimated 50.000
-- TABLE VARIABLE
----------------------------
DECLARE @MasterId AS TABLE( MasterId SMALLINT )
INSERT INTO @MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN @MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
GO
-- Actual: 489, Estimated 40.000
-- TEMP TABLE
----------------------------
CREATE TABLE #MasterId( MasterId SMALLINT )
INSERT INTO #MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d --WITH (FORCESEEK)
INNER JOIN #MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
-- Actual 489, Estimated 489
DROP TABLE #MasterId
GO
分析と最終質問
「インデックス シーク (非クラスター化) [DetailTable].[IX_DetailTable]」の操作をご覧ください。
上記のスクリプトのコメントは、推定および実際の行数について取得した値を示しています。
私たちの実稼働環境では、このテーブルには 3,300 万行があり、上記のクエリの推定行数は 300 万から 1,600 万まで異なります。
要約する:
DetailTable と MasterTable の間の結合が行われると、推定行数は 12.5% です (マスター テーブルには 8 つの値があり、理にかなっています...)
DetailTable とテーブル変数の間の結合が行われると、推定行数は 10% です。
DetailTable と一時テーブルの間の結合が行われると、推定される行数は実際の行数とまったく同じになります
問題は、なぜこれらの値が異なるのかということです。
統計は最新のものであり、見積もりを行うのは本当に簡単です。
これだけは理解しておきたい。