Pendant quelques jours, j'ai eu du mal à améliorer les performances de ma base de données et il y a quelques problèmes qui me confus concernant l'indexation dans une base de données SQL Server.
J'essaierai d'être aussi informatif que possible.
Ma base de données contient actuellement environ 100 000 lignes et continuera de croître, c'est pourquoi j'essaie de trouver un moyen de le faire fonctionner plus rapidement.
J'écris également dans ce tableau, donc si votre suggestion réduira considérablement le temps d'écriture, faites-le moi savoir.
L'objectif global est de sélectionner toutes les lignes avec des noms spécifiques qui se trouvent dans une plage de dates.
Ce sera généralement de sélectionner plus de 3000 lignes sur un lot lol ...
Schéma de table:
CREATE TABLE [dbo].[reports]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[IsDuplicate] [bit] NOT NULL,
[IsNotValid] [bit] NOT NULL,
[Time] [datetime] NOT NULL,
[ShortDate] [date] NOT NULL,
[Source] [nvarchar](350) NULL,
[Email] [nvarchar](350) NULL,
CONSTRAINT [PK_dbo.reports]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
Voici la requête SQL que j'utilise:
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate BETWEEN '2017-10-13' AND '2017-10-15'
Comme je l'ai compris, ma meilleure approche pour améliorer l'efficacité sans nuire au temps d'écriture serait de créer un index non cluster sur les Source
et ShortDate
.
Ce que j'ai aimé tel, le schéma d'indexation:
CREATE NONCLUSTERED INDEX [Source&Time]
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)
Maintenant, nous arrivons à la partie délicate qui m'a complètement perdu, l'index ci-dessus fonctionne parfois, parfois à moitié fonctionne et parfois ne fonctionne pas du tout ....
(Je ne sais pas si cela importe, mais actuellement 90% des lignes de la base de données ont la même source, bien que cela ne restera pas longtemps comme ça)
Avec la requête ci-dessous, l'index n'est pas utilisé du tout, j'utilise SQL Server 2014 et dans le plan d'exécution, il indique qu'il n'utilise que l'analyse d'index en cluster:
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
Avec cette requête, l'index n'est pas utilisé du tout, bien que je reçoive une suggestion de SQL Server pour créer un index avec la date en premier et la source en second ... J'ai lu que l'index devrait être fait par l'ordre de la requête est? Il est également indiqué d'inclure toutes les colonnes que je sélectionne, est-ce un must? ... encore une fois, j'ai lu que je ne devrais inclure dans l'index que les colonnes que je recherche.
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate = '2017-10-13'
Suggestion d'index SQL Server -
/* The Query Processor estimates that implementing the following
index could improve the query cost by 86.2728%. */
/*
USE [db]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[reports] ([ShortDate], [Source])
INCLUDE ([id], [IsDuplicate], [IsNotValid], [Time], [Email])
GO
*/
Maintenant, j'ai essayé d'utiliser l'index que SQL Server m'a suggéré de faire et cela fonctionne, il semble qu'il utilise 100% de l'index non cluster en utilisant les deux requêtes ci-dessus.
J'ai essayé d'utiliser cet index mais en supprimant les colonnes incluses et cela ne fonctionne pas ... semble que je dois inclure dans l'index toutes les colonnes que je sélectionne?
BTW ça marche aussi quand on utilise l'index que j'ai fait si j'inclus toutes les colonnes.
Pour résumer: il semble que l'ordre de l'index n'ait pas d'importance, car il fonctionnait à la fois lors de la création de Source + ShortDate
et ShortDate + Source
Mais pour une raison quelconque, il est indispensable d'inclure toutes les colonnes ... (ce qui affectera considérablement l'écriture dans ce tableau?)
Merci beaucoup d'avoir lu, Mon objectif est de comprendre pourquoi ce truc se passe et ce que je dois faire autrement (pas seulement la solution car je devrai l'appliquer sur d'autres projets aussi).
À votre santé :)
L'indexation dans SQL Server est en partie le savoir-faire d'une longue expérience (et de nombreuses heures de frustration) et en partie de la magie noire. Ne vous en faites pas trop - c'est un endroit comme SO est idéal pour - beaucoup de cerveaux, beaucoup d'expérience de plusieurs heures d'optimisation, que vous pouvez exploiter.
J'ai lu que l'index doit être fait dans l'ordre de la requête.
Si vous lisez ceci - c'est absolument PAS VRAI - l'ordre des colonnes est pertinent - mais dans un autre sens façon: un index composé (composé de plusieurs colonnes) ne sera jamais pris en compte que si vous spécifiez n colonnes les plus à gauche dans la définition d'index dans votre requête.
Exemple classique: un annuaire avec un index sur (ville, nom, prénom). Un tel index peut être utilisé :
WHERE
city
et lastname
(trouver tous les "Miller" dans "Detroit")mais il peut JAMAIS JAMAIS être utilisé si vous voulez rechercher uniquement firstname
..... c'est l'astuce sur les index composés que vous besoin d'être au courant. Mais si vous utilisez toujours toutes les colonnes d'un index, leur ordre n'est généralement pas vraiment pertinent - l'optimiseur de requête s'en occupera pour vous.
Quant aux colonnes incluses - celles-ci sont stockées seulement dans le niveau feuille de l'index non cluster - elles sont [~ # ~] pas [ ~ # ~] partie de la structure de recherche de l'index, et vous ne pouvez pas spécifier de valeurs de filtre pour les colonnes incluses dans votre clause WHERE
.
Le principal avantage de ces colonnes incluses est le suivant: si vous effectuez une recherche dans un index non clusterisé, et finalement, vous trouvez la valeur que vous recherchez - de quoi disposez-vous à ce stade? L'index non cluster stockera les colonnes dans la définition d'index non cluster (ShortDate
et Source
), et il stockera la clé de clustering (si vous avez un - et vous devrait !) - mais rien d'autre.
Donc, dans ce cas, une fois qu'une correspondance est trouvée et que votre requête veut tout à partir de cette table, SQL Server doit faire ce qu'on appelle une recherche de clé ( souvent appelé aussi recherche de signet ) dans lequel il prend la clé en cluster et effectue ensuite une opération Seek contre l'index clusterisé, pour accéder à la page de données réelle qui contient toutes les valeurs que vous recherchez.
Si vous avez colonnes incluses dans votre index, alors la page de niveau feuille de votre non clusterisé contient
INCLUDE
Si ces colonnes "couvrent" votre requête, par exemple fournissez toutes les valeurs dont votre requête a besoin, puis SQL Server est terminé une fois qu'il trouve la valeur que vous avez recherchée dans l'index non clusterisé - il peut prendre toutes les valeurs dont il a besoin à partir de cette page de niveau feuille de l'index non clusterisé, et il n'a PAS besoin pour effectuer une autre recherche de clé (coûteuse) dans l'index de clustering pour obtenir les valeurs réelles.
Pour cette raison, essayer de toujours spécifier explicitement uniquement les colonnes dont vous avez vraiment besoin dans votre SELECT
peut être bénéfique - dans ce cas, vous pourriez créer un index couvrant qui fournit toutes les valeurs pour votre SELECT
- toujours en utilisant SELECT *
rend cela vraiment difficile ou presque impossible .....
En général, vous voulez que l'index soit du plus sélectif (c'est-à-dire filtrant le plus d'enregistrements possible) au moins sélectif; si une colonne a une faible cardinalité, l'optimiseur de requête peut l'ignorer.
Cela a un sens intuitif - si vous avez un annuaire téléphonique et que vous recherchez des personnes appelées "smith", avec le "A" initial, vous voulez commencer par rechercher "smith" d'abord, puis les "A" s , plutôt que toutes les personnes dont l'initiale est "A", puis filtrer celles appelées "Smith". Après tout, les chances sont qu'une personne sur 26 a le "A" initial.
Donc, dans votre exemple, je suppose que vous avez un large éventail de valeurs en date courte - c'est donc la première colonne que l'optimiseur de requête essaie de filtrer. Vous dites que vous avez peu de valeurs différentes dans "source", donc l'optimiseur de requête peut décider de l'ignorer; dans ce cas, la deuxième colonne de cet index ne sert à rien non plus.
L'ordre des clauses where dans l'index n'a pas d'importance - vous pouvez les permuter et obtenir exactement les mêmes résultats, de sorte que l'optimiseur de requête les ignore.
ÉDITER:
Alors, oui, faites l'index. Imaginez que vous ayez un tas de cartes à trier - lors de votre première manche, vous souhaitez retirer autant de cartes que possible. En supposant que tout est uniformément réparti - si vous avez 1000 short_dates distinctes sur un million de lignes, cela signifie que vous vous retrouvez avec 1000 éléments si votre première exécution commence le short_date; si vous triez par source, vous disposez de 100 000 lignes.
Les colonnes incluses d'un index correspondent aux colonnes que vous sélectionnez. En raison du fait que vous faites select *
(ce qui n'est pas une bonne pratique), l'index ne sera pas utilisé, car il devra rechercher toute la table pour obtenir les valeurs des colonnes.
Pour votre scénario, je supprimerais l'index cluster par défaut (s'il y en a un) et créerais un nouvel index cluster avec la déclaration suivante:
USE [db]
GO
CREATE CLUSTERED INDEX CIX_reports
ON [dbo].[reports] ([ShortDate],[Source])
GO