web-dev-qa-db-fra.com

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

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 la structure de la 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

enter image description here

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

9
Registered User

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 (FirstName et LastName).

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

Ainsi, les coûts estimés reflètent le plus grand nombre de lectures et IO exigence.

Si vous deviez déclarer le NCI

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

il serait alors similaire à l'index clusterisé.

10
Martin Smith

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.

5
Nenad Zivkovic

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'index non cluster de T3 est de 97 Mo, soit environ 95% de moins IO que T1.

Donc, sur la base du IO requis, le plan de requête d'origine aurait dû être plus dans le sens de 10%/90%, pas 38%/62%. De plus, puisque le non clusterisé index est susceptible de tenir entièrement en mémoire, la différence peut être encore plus grande, car le disque IO est très cher.

2
StrayCatDBA