web-dev-qa-db-ja.com

非クラスター化インデックスはクラスター化インデックスよりも高速ですか?

両方のテーブルは同じ構造で、各テーブルに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

enter image description here

クラスター化インデックスが62%で非クラスター化38%を占める理由

9
Registered User

はい、クラスター化インデックスのリーフページは他の2つの列(FirstNameおよびLastName)の値を格納する必要があるため、クラスター化インデックスのページあたりの行数は非クラスター化インデックスよりも少なくなります。

NCIのリーフページには、BusinessEntityId値と行ロケーター(テーブルがヒープの場合はRID、それ以外の場合はCIキー)のみが格納されます。

したがって、推定コストは、より多くの読み取りとIO要件を反映しています。

NCIを次のように宣言する場合

nonclustered index on Persontb_NC(BusinessEntityId) INCLUDE (FirstName, LastName)

その後、それはクラスタ化インデックスに似ています。

10
Martin Smith

クラスター化インデックスには、列インデックスのデータがオンになっているだけでなく、他のすべての列のデータも含まれています。 (テーブルごとにクラスター化インデックスは1つしかありません)

非クラスター化インデックスには、インデックス付きの列からのデータと、残りのデータがある場所へのrow_idポインターのみが含まれます。

したがって、この特定の非クラスター化インデックスは軽く、スキャン/シークするために必要な読み取りが少なくなり、この特定のクエリはより高速に動作します。

ただし、FirstNameとLastNameも取得しようとした場合、それは異なり、クラスター化インデックスのパフォーマンスが向上します。

5
Nenad Zivkovic

クエリプラン間の割合は、完全に比較しても意味がありません。有効な比較を行うには、クエリをベンチマークする必要があります。さらに、行数が少ないと、索引付け戦略間のパフォーマンスの違いが隠される傾向があります。行数を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は非常に高価であるため、差異はさらに大きくなる可能性があります。

2
StrayCatDBA