web-dev-qa-db-fra.com

Comment SQL Server détermine-t-il l'ordre des colonnes clés dans les demandes d'index manquantes?

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?

32
Bryan Rebok

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:

Execution plans with 3 equality fields

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:

  • Sur dbo.NumberLetterDate, l'index manquant se trouve sur fINT (nombre), fLetter (nvarchar), fDate, le même ordre des champs de la table
  • Sur dbo.LetterDateNumber, l'ordre d'index passe à fNVARCHAR, fDATE, fINT
  • Sur dbo.DateNumberLetter, l'ordre d'index passe à fDATE, fINT, fNVARCHAR

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é:

Execution plans with 2 equality, and 1 inequality search

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:

Execution plans with 3 inequality searches

44
Bryan Rebok