L'index non clusterisé est plus rapide que l'index clusterisé?

9

Les deux tables ont la même structure et 19972 lignes dans chaque table. pour pratiquer l'indexation, j'ai créé les deux tables ayant la même structure et créé

clustered index on persontb(BusinessEntityID)

et

nonclustered index on Persontb_NC(BusinessEntityId)

et structure de table

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

entrez la description de l'image ici

Pourquoi l'index clusterisé prend 62% et non clusterisé 38%?


la source
1
Pourquoi voter pour close?

Réponses:

10

Oui, l'index cluster a moins de lignes par page que l'index non cluster car les pages feuilles de l'index cluster doivent stocker les valeurs des deux autres colonnes ( FirstNameet LastName).

Les pages feuilles du NCI ne stockent que les BusinessEntityIdvaleurs et un localisateur de lignes (RID si la table est un tas ou la clé CI sinon).

Les coûts estimés reflètent donc le plus grand nombre de lectures et les besoins d'E / S.

Si vous deviez déclarer le NCI

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

il serait alors similaire à l'index clusterisé.

Martin Smith
la source
5

L'index clusterisé contient non seulement les données de l'index de colonne, mais également les données de toutes les autres colonnes. (Il ne peut y avoir qu'un seul index cluster par table)

L'index non cluster contient uniquement les données des colonnes indexées et un pointeur row_id vers l'emplacement du reste des données.

Par conséquent, cet index non cluster particulier est plus léger et moins de lecture est nécessaire pour le parcourir / le rechercher et cette requête particulière fonctionnera plus rapidement.

Cependant, si vous avez également essayé de récupérer FirstName et LastName, ce serait différent et l'index cluster devrait mieux fonctionner.

Nenad Zivkovic
la source
2

Les pourcentages entre les plans de requête n'ont aucun sens à comparer purement et simplement. Vous devez comparer les requêtes pour avoir une comparaison valide. En outre, les petits nombres de lignes ont tendance à masquer les différences de performances entre les stratégies d'indexation. En augmentant le nombre de lignes à 10 millions, vous pouvez obtenir une image plus claire des différences de performances.

Il existe un exemple de script qui crée 3 tables, vos deux d'en haut et une troisième avec un index cluster et non cluster.

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)

Remplissez les tables avec 10 millions de lignes

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'

Nous pouvons utiliser sys.dm_db_index_physical_stats pour voir la taille sur disque des index.

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 

Et les résultats:

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

L'index cluster de T1 mesure environ 1,6 Go. L'indice non clusterisé de T2 est de 170 Mo (90% d'économie d'E / S). L'indice non cluster de T3 est de 97 Mo, soit environ 95% d'E / S en moins que T1.

Ainsi, basé sur les E / S requises, le plan de requête d'origine aurait dû être plus proche de 10% / 90%, et non 38% / 62%. De plus, étant donné que l'index non clusterisé est susceptible de tenir entièrement en mémoire, la différence peut être encore plus grande, car les E / S disque sont très coûteuses.

StrayCatDBA
la source
1
C'est un peu un saut à déduire que votre 10%/90%chiffre est plus précis que le 38%/62%. Les chaînes d'une longueur comprise entre 100 et 200 constitueront certainement une surestimation brute des besoins en espace pour une paire prénom / nom, vous aurez donc une densité de page inférieure à celle de l'OP. Lorsque j'essaie par rapport à vos données d'exemple, les coûts estimés sont de 87% / 13% .
Martin Smith
1
SQL Server fait déjà référence à data_pagesin sys.allocation_units. Vous pouvez le voir à partir de CREATE TABLE T1(C INT);CREATE TABLE T2(C INT);UPDATE STATISTICS T1 WITH PAGECOUNT = 1;UPDATE STATISTICS T2 WITH PAGECOUNT = 100là en comparant les coûts estimésSELECT * FROM T1;SELECT * FROM T2;
Martin Smith
Veuillez relire la première phrase de ma réponse. La comparaison directe des coûts n'a aucun sens. Pour la différence de performance entre les requêtes de l'OP, une meilleure estimation peut être dérivée empiriquement en calculant la réduction de la taille des index (et donc du nombre d'E / S), et non par les coûts de l'optimiseur.
StrayCatDBA
1
De manière générale, c'est oui, mais dans ce cas, la raison pour laquelle l'optimiseur de requêtes coûte plus cher l'index cluster que l'index non cluster (le sujet de cette question) est précisément en raison des différents nombres de pages.
Martin Smith
1
Selon http://www.qdpma.com/ppt/CostFormulas2.ppt La formule utilisée pour le coût d' une analyse d' index ou Index Seek sans recherche est (version dépendante) IO (+ 0,003125 0,00074074 par page) et CPU (+ 0,0001581 0,0000011 Par rangée). Les coûts fixes et les lignes sont égaux pour CI et NCI, la seule variable est donc les pages.
Martin Smith