Comment SQL Server détermine-t-il l'ordre des colonnes clés dans ses recommandations d'index manquantes pour un plan de requête?
Lorsque SQL Server crée une recommandation d'index manquante pour un plan de requête particulier, il sépare les colonnes clés possibles en 2 groupes. Le premier ensemble contient toutes les colonnes recommandées qui font partie d'un prédicat EQUALITY. Le deuxième ensemble contient toutes les colonnes recommandées qui font partie d'un prédicat INEQUALITY.
Dans chaque ensemble, les colonnes sont classées selon la position ordinale des colonnes, en fonction de la définition de la table.
(Un grand merci à Brent Ozar pour la construction d'un script de repro contre la base de données Stack Overflow pour le prouver!)
1. Créez 3 tableaux identiques , mais mettez leurs colonnes dans un ordre différent. (La raison ici est d'utiliser une variété de noms de colonnes et de types de données pour montrer que cela n'a pas d'impact sur l'ordre des colonnes dans la recommandation d'index manquante.)
CREATE TABLE dbo.NumberLetterDate (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
fINT INT, fNVARCHAR NVARCHAR(40), fDATE DATETIME, AboutMe NVARCHAR(MAX));
GO
CREATE TABLE dbo.LetterDateNumber (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
fNVARCHAR NVARCHAR(40), fDATE DATETIME, fINT INT, AboutMe NVARCHAR(MAX));
GO
CREATE TABLE dbo.DateNumberLetter (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
fDATE DATETIME, fINT INT, fNVARCHAR NVARCHAR(40), AboutMe NVARCHAR(MAX));
GO
2. Remplissez les tables avec les mêmes données. Obtenez 100 000 lignes de la table Users avec une distribution de données réelle.
INSERT INTO dbo.NumberLetterDate(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
FROM dbo.Users WITH (NOLOCK)
ORDER BY Id;
GO
INSERT INTO dbo.LetterDateNumber(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
FROM dbo.Users WITH (NOLOCK)
ORDER BY Id;
GO
INSERT INTO dbo.DateNumberLetter(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
FROM dbo.Users WITH (NOLOCK)
ORDER BY Id;
GO
3. Écrivez une requête qui a besoin d'un index. Commencez avec 3 filtres d'égalité, en filtrant pour une valeur exacte dans les 3 champs. Notez que les 3 requêtes ont les mêmes champs dans le même ordre:
SELECT ID
FROM dbo.NumberLetterDate
WHERE fINT = 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.LetterDateNumber
WHERE fINT = 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.DateNumberLetter
WHERE fINT = 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
GO
Les trois tables contiennent exactement les mêmes données et les requêtes sont identiques. La seule différence est l'ordre des champs - et c'est aussi la différence dans nos demandes d'index manquantes:
Dans les plans d'exécution, l'ordre des colonnes dans la demande d'index manquante correspond exactement à l'ordre des colonnes dans la table. Par exemple, dans dbo.NumberLetterDate, la colonne numérique est la première, donc elle est également la première dans la demande d'index manquante:
Pour une opération à table unique comme celle-ci, l'ordre des champs d'index ne semble pas dépendre de la sélectivité, du type de données ou de la position dans la requête. (Je laisse le soin à d'autres personnes de le prouver avec des requêtes et des jointures plus complexes.)
4. Ajoutez un filtre d'inégalité. Dans le champ INT, par exemple, mettez <> 100 comme filtre:
SELECT ID
FROM dbo.NumberLetterDate
WHERE fINT <> 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.LetterDateNumber
WHERE fINT <> 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.DateNumberLetter
WHERE fINT <> 100
AND fNVARCHAR = 'Brent Ozar'
AND fDATE = '2018/01/01'
AND 1 = (SELECT 1);
GO
Dans les plans d'exécution, les champs d'égalité passent en premier, puis les champs d'inégalité - ici, FINT apparaît en dernier dans les 3 demandes d'index manquantes car il s'agit d'une recherche d'inégalité:
5. Utilisez 3 filtres d'inégalité. Utilisez la même recherche pour tous les champs (<>):
SELECT ID
FROM dbo.NumberLetterDate
WHERE fINT <> 100
AND fNVARCHAR <> 'Brent Ozar'
AND fDATE <> '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.LetterDateNumber
WHERE fINT <> 100
AND fNVARCHAR <> 'Brent Ozar'
AND fDATE <> '2018/01/01'
AND 1 = (SELECT 1);
SELECT ID
FROM dbo.DateNumberLetter
WHERE fINT <> 100
AND fNVARCHAR <> 'Brent Ozar'
AND fDATE <> '2018/01/01'
AND 1 = (SELECT 1);
GO
Comme il n'y a pas de recherche d'égalité, les 3 champs ont le même ordre de priorité dans la recommandation d'index manquante, et maintenant nous sommes de retour au tri uniquement par ordre de champ: