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


9

両方のテーブルは同じ構造で、各テーブルに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%になるのはなぜですか?


1
なぜクローズに投票するのですか?

回答:


10

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

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

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

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

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

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


5

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

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

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

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


2

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


1
10%/90%数値がの数値よりも正確であると推測するのは少し進歩38%/62%です。100から200までの長さの文字列は、ファーストネームとラストネームのペアに必要なスペースの総量を確実に過大評価するため、OPよりもページ密度が低くなります。私があなたの例のデータを試してみると、推定コストは87%/ 13%と表示されます。
マーティン・スミス

1
SQL Serverは、data_pagesを参照していsys.allocation_unitsます。CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100これから、推定コストを比較することでこれを確認できますSELECT * FROM T1;SELECT * FROM T2;
マーティンスミス

私の回答の最初の文をもう一度読んでください。コストを直接比較しても意味がありません。OPのクエリ間のパフォーマンスの違いについては、オプティマイザからのコストではなく、インデックスのサイズ(したがってIOの数)の削減を計算することにより、経験的により適切な見積もりを導き出すことができます。
StrayCatDBA 2013年

1
一般的にはそうですが、この場合、クエリオプティマイザーが非クラスター化インデックス(この質問の主題)よりもクラスター化インデックスにコストをかける理由は、ページ数が異なるためです。
マーティン・スミス

1
よるhttp://www.qdpma.com/ppt/CostFormulas2.pptルックアップすることなく、シークインデックススキャンまたはインデックスをコストに使用される式(バージョン依存)IO(ページあたり0.003125 + 0.00074074)とCPUである(+ 0.0000011 0.0001581行あたり)。固定費と行はCIとNCIで等しいため、唯一の変数はページです。
マーティン・スミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.