両方のテーブルは同じ構造で、各テーブルに19972行あります。インデックス作成を練習するために、同じ構造を持つ両方のテーブルを作成し、
clustered index on persontb(BusinessEntityID)
そして
nonclustered index on Persontb_NC(BusinessEntityId)
およびテーブル構造
BusinessEntityID int
FirstName varchar(100)
LastName varchar(100)
-- Nonclusted key on businessentityid takes 38%
SELECT BusinessEntityId from Persontb_NC
WHERE businessentityid BETWEEN 400 AND 4000
-- CLustered key businessentityid takes 62%
SELECT BusinessEntityId from persontb
WHERE businessentityid BETWEEN 400 AND 4000
クラスター化インデックスが62%で非クラスター化38%を占める理由
はい、クラスター化インデックスのリーフページは他の2つの列(FirstName
およびLastName
)の値を格納する必要があるため、クラスター化インデックスのページあたりの行数は非クラスター化インデックスよりも少なくなります。
NCIのリーフページには、BusinessEntityId
値と行ロケーター(テーブルがヒープの場合はRID、それ以外の場合はCIキー)のみが格納されます。
したがって、推定コストは、より多くの読み取りとIO要件を反映しています。
NCIを次のように宣言する場合
nonclustered index on Persontb_NC(BusinessEntityId) INCLUDE (FirstName, LastName)
その後、それはクラスタ化インデックスに似ています。
クラスター化インデックスには、列インデックスのデータがオンになっているだけでなく、他のすべての列のデータも含まれています。 (テーブルごとにクラスター化インデックスは1つしかありません)
非クラスター化インデックスには、インデックス付きの列からのデータと、残りのデータがある場所へのrow_idポインターのみが含まれます。
したがって、この特定の非クラスター化インデックスは軽く、スキャン/シークするために必要な読み取りが少なくなり、この特定のクエリはより高速に動作します。
ただし、FirstNameとLastNameも取得しようとした場合、それは異なり、クラスター化インデックスのパフォーマンスが向上します。
クエリプラン間の割合は、完全に比較しても意味がありません。有効な比較を行うには、クエリをベンチマークする必要があります。さらに、行数が少ないと、索引付け戦略間のパフォーマンスの違いが隠される傾向があります。行数を1000万に増やすと、パフォーマンスの違いをより明確に把握できます。
3つのテーブルを作成するサンプルスクリプトがあり、2つは上から、3つ目はクラスター化インデックスと非クラスター化インデックスの両方を使用します。
USE [tempdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[t1](
[id] [int] IDENTITY(1,1) NOT NULL,
[c1] [varchar](200) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[t2](
[id] [int] IDENTITY(1,1) NOT NULL,
[c1] [varchar](200) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[t3](
[id] [int] IDENTITY(1,1) NOT NULL,
[c1] [varchar](200) NULL
) ON [PRIMARY]
GO
CREATE CLUSTERED INDEX CIX_t1 ON t1(id)
CREATE NONCLUSTERED INDEX IX_t2 ON t2(id)
CREATE CLUSTERED INDEX CIX_t3 ON t3(id)
CREATE NONCLUSTERED INDEX IX_t3 ON t3(id)
テーブルに1,000万行を入力します
DECLARE @i INT
DECLARE @j int
DECLARE @t DATETIME
SET NOCOUNT ON
SET @t = CURRENT_TIMESTAMP
SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200
INSERT INTO t1 (c1) VALUES (REPLICATE('x', 101+ CAST(Rand(@i) * 100 AS INT)))
SET @i = @i + 1
END
PRINT 'Time to populate t1: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'
SET @t = CURRENT_TIMESTAMP
SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200
INSERT INTO t2 (c1) VALUES (REPLICATE('x', 101+ CAST(Rand(@i) * 100 AS INT)))
SET @i = @i + 1
END
PRINT 'Time to populate t3: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'
SET @t = CURRENT_TIMESTAMP
SET @i = 0
WHILE @i < 10000000
BEGIN
--populate with strings with a length between 100 and 200
INSERT INTO t3 (c1) VALUES (REPLICATE('x', 101+ CAST(Rand(@i) * 100 AS INT)))
SET @i = @i + 1
END
PRINT 'Time to populate t3: '+ CAST(DATEDIFF(ms, @t, CURRENT_TIMESTAMP) AS VARCHAR(10)) + ' ms'
Sys.dm_db_index_physical_statsを使用して、インデックスのディスク上のサイズを確認できます。
SELECT OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc,
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t1'), NULL, NULL, 'detailed')
WHERE index_level = 0
UNION ALL
SELECT OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc,
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t2'), NULL, NULL, 'detailed')
WHERE index_level = 0
UNION ALL
SELECT OBJECT_NAME(OBJECT_ID) table_name, index_id, index_type_desc,
record_count, page_count, page_count / 128.0 size_in_mb, avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('t3'), NULL, NULL, 'detailed')
WHERE index_level = 0
そして結果:
table_name index_id page_count size_in_mb avg_record_size_in_bytes index_type_desc
t1 1 211698 1653.890625 167.543 CLUSTERED INDEX
t2 0 209163 1634.085937 165.543 HEAP
t2 2 22272 174.000000 16 NONCLUSTERED INDEX
t3 1 211698 1653.890625 167.543 CLUSTERED INDEX
t3 2 12361 96.570312 8 NONCLUSTERED INDEX
T1のクラスター化インデックスのサイズは約1.6 GBです。 T2の非クラスター化インデックスは170 MB(IOの90%節約)です。 T3の非クラスター化インデックスは97 MB、つまりT1よりも約95%少ないIO.
したがって、IOが必要であることに基づいて、元のクエリプランは、38%/ 62%ではなく、10%/ 90%のラインに沿っているはずです。インデックスは完全にメモリに収まる可能性が高く、ディスクIOは非常に高価であるため、差異はさらに大きくなる可能性があります。