Je testais les index SQL Server et j'ai trouvé un comportement très étrange. Voici mon code:
DROP TABLE IF EXISTS dbo._Test
DROP TABLE IF EXISTS dbo._Newtest
GO
CREATE TABLE _Test(
ID INT NOT NULL,
UserSystemID INT NOT NULL,
Age INT
)
GO
INSERT INTO dbo._Test
( ID, UserSystemID, Age )
SELECT TOP 10000000 ABS(CHECKSUM(NEWID())) % 5000000, ABS(CHECKSUM(NEWID())) % 2, ABS(CHECKSUM(NEWID())) % 100
FROM sys.all_columns
CROSS JOIN sys.all_objects a
CROSS JOIN sys.all_objects b
CROSS JOIN sys.all_objects c
; WITH cte AS (
SELECT ID, UserSystemID, age, ROW_NUMBER() OVER(PARTITION BY ID, UserSystemID ORDER BY GETDATE()) rn
FROM dbo._Test
)
SELECT cte.ID ,
cte.UserSystemID ,
cte.Age
INTO _newTest
FROM cte
WHERE cte.rn = 1
CREATE UNIQUE NONCLUSTERED INDEX IX_test ON dbo._NewTest(ID, UserSystemID) INCLUDE(age)
GO
ALTER TABLE dbo._NewTest ADD CONSTRAINT PK_NewTest PRIMARY KEY CLUSTERED(UserSystemID, ID)
GO
À ce stade, j'ai deux index sur la même table et sur les mêmes colonnes. Le premier est non cluster et le second est groupé. La colonne Id
est plus sélective (environ 5000000 valeurs uniques) et UserSystemID
ne l'est pas (deux valeurs uniques).
Ensuite, j'exécute la requête suivante pour tester quel index est utilisé:
SELECT id, UserSystemID, age
FROM _NewTest
WHERE id = 1502945
AND UserSystemID = 1
Il recherche l'index clusterisé. Vous pouvez voir le plan ici .
La question est de savoir pourquoi SQL Server préfère l'index clusterisé à l'index unique non cluster.
Ma première colonne d'index cluster est beaucoup moins sélective que l'autre index unique non cluster. Je m'attends donc à ce que les performances soient moins bonnes avec un index clusterisé mais en pratique ce n'est pas le cas.
Compte tenu des index uniques, votre requête sélectionnera au plus une ligne.
L'optimiseur sait qu'il devra descendre l'arborescence b de l'index une seule fois, et n'aura pas besoin de parcourir en avant ou en arrière à partir de ce point pour trouver plus de correspondances. Ceci est connu comme une recherche de singleton (test d'égalité sur un index unique).
L'implémentation de correspondance d'index actuelle arrive à toujours choisir l'index clusterisé lorsqu'elle peut utiliser une recherche singleton.
Le choix entre un index clusterisé et non cluster ici n'est généralement pas très important. Il pourrait y avoir un minuscule coût supplémentaire lorsque les niveaux supérieurs de l'arbre b sont parcourus (en utilisant la recherche binaire ou l'interpolation linéaire) mais cela serait difficile à même mesurer. N'oubliez pas que seuls les composants clés ID
et UserSystemID
sont présents sur les pages d'index non foliaires.
On pourrait faire valoir que les pages de feuilles d'index regroupées plus larges sont moins susceptibles d'être en mémoire en moyenne. Il y a quelques autres cas Edge conséquences, mais je ne vois pas ce comportement être modifié de si tôt.
Mais ma première colonne d'index clusterisé est beaucoup moins sélective que l'autre index unique non clusterisé. Je m'attends donc à ce que les performances soient moins bonnes avec un index clusterisé, mais en pratique ce n'est pas le cas.
La sélectivité n'a pas d'importance pour la recherche d'égalité sur un index b-tree composé.
Votre index composé unique en cluster possède des clés (UserSystemID, id).
Pour rechercher une ligne avec (UserSystemID = 1 et id = 1502945), SQL Server ne trouve pas toutes les lignes où UserSystemID = 1, puis ne trouve pas les lignes où id = 1502945. Ce serait très inefficace.
Vous pouvez savoir combien de pages votre requête de test touche en utilisant SET STATISTICS IO ON
. Votre exemple crée un index cluster avec deux niveaux non-feuilles. Au total, trouver la ligne souhaitée signifie toucher trois pages - une à chaque niveau de l'index.
Les lignes sont classées dans l'index par UserSystemID et id. Ma copie de votre table de démonstration a la disposition suivante sur la page racine (niveau supérieur) de l'index clusterisé:
Il est facile d'effectuer une recherche binaire sur cette page:
Suivant cette logique, nous trouverons rapidement la page d'index enfant (niveau inférieur suivant) sur laquelle les clés recherchées seront trouvées si elles sont présentes. Répétez la recherche binaire sur cette page, et ainsi de suite jusqu'à ce que nous atteignions la page au niveau feuille unique qui doit contenir la ligne que nous recherchons, si elle existe.