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
Pourquoi l'index clusterisé prend 62% et non clusterisé 38%?
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é.
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.
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.