J'ai une requête qui prend actuellement en moyenne 2500 ms pour être terminée. Ma table est très étroite, mais il y a 44 millions de lignes. Quelles options dois-je pour améliorer les performances, ou est-ce aussi bon que possible?
La requête
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
La table
CREATE TABLE [dbo].[Heartbeats](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DeviceID] [int] NOT NULL,
[IsPUp] [bit] NOT NULL,
[IsWebUp] [bit] NOT NULL,
[IsPingUp] [bit] NOT NULL,
[DateEntered] [datetime] NOT NULL,
CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
L'index
CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats]
(
[DateEntered] ASC,
[DeviceID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
L'ajout d'index supplémentaires aiderait-il? Si oui, à quoi ressembleraient-ils? Les performances actuelles sont acceptables, car la requête n'est exécutée qu'occasionnellement, mais je me demande, en tant qu'exercice d'apprentissage, que puis-je faire pour accélérer le processus?
MISE À JOUR
Lorsque je modifie la requête pour utiliser un indice d'index de force, la requête s'exécute en 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
L'ajout d'une clause DeviceID correctement sélective atteint également la plage de 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;
Si j'ajoute ORDER BY [DateEntered], [DeviceID]
à la requête d'origine, je suis dans la plage des 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
ORDER BY [DateEntered], [DeviceID];
Tout cela utilise l'index que j'attendais (CommonQueryIndex) donc, je suppose que ma question est maintenant, existe-t-il un moyen de forcer cet index à être utilisé sur des requêtes comme celle-ci? Ou la taille de ma table rejette-t-elle trop l'optimiseur et je dois simplement utiliser un ORDER BY
ou un indice?
Réponses:
Pourquoi l'optimiseur ne choisit pas votre premier index:
Est une question de sélectivité de la colonne [DateEntered].
Vous nous avez dit que votre table compte 44 millions de lignes. la taille de la ligne est:
4 octets, pour l'ID, 4 octets pour l'ID de périphérique, 8 octets pour la date et 1 octet pour les colonnes de 4 bits. cela représente 17 octets + 7 octets de surcharge pour (balises, bitmap nul, décalage de col var, nombre de col) totalisant 24 octets par ligne.
Cela se traduirait grossièrement par 140 000 pages. Pour stocker ces 44 millions de lignes.
L'optimiseur peut maintenant faire deux choses:
À un certain moment, il devient plus coûteux d'effectuer toutes ces recherches uniques dans l'index cluster pour chaque entrée d'index trouvée dans votre index non cluster. Le seuil pour cela est généralement le nombre total de recherches doit dépasser 25% à 33% du nombre total de pages de table.
Donc dans ce cas: 140k / 25% = 35000 lignes 140k / 33% = 46666 lignes.
(@RBarryYoung, 35k représente 0,08% du total des lignes et 46666 est 0,10%, donc je pense que c'est là que la confusion était)
Donc, si votre clause where se traduira par quelque part entre 35000 et 46666 lignes (c'est sous la clause supérieure!), Il est très probable que votre non cluster ne sera pas utilisé et que l'analyse d'index cluster sera utilisée.
Les deux seules façons de changer cela sont:
maintenant sûr que vous pouvez créer un index de couverture même lorsque vous utilisez un select *. Cependant, cela ne fait que créer une surcharge énorme pour vos insertions / mises à jour / suppressions. Il nous faudrait en savoir plus sur votre charge de travail (lecture vs écriture) pour vous assurer que c'est la meilleure solution.
Le passage de datetime à smalldatetime représente une réduction de 16% de la taille de l'index cluster et une réduction de 24% de la taille de votre index non cluster.
la source
Y a-t-il une raison particulière pour laquelle votre PK est en cluster? Beaucoup de gens le font parce que c'est par défaut de cette façon, ou ils pensent que les PK doivent être groupés. Non. Les index clusterisés sont généralement les meilleurs pour les requêtes de plage (comme celle-ci) ou sur la clé étrangère d'une table enfant.
Un indice de clustering a pour effet de regrouper toutes les données car les données sont stockées sur les nœuds terminaux de l'arborescence du cluster b. Donc, en supposant que vous ne demandez pas une plage «trop large», l'optimiseur saura exactement quelle partie de l'arborescence b contient les données et il n'aura pas à trouver un identificateur de ligne, puis sautera jusqu'à l'endroit où les données est (comme il le fait lorsqu'il s'agit d'un index NC). Qu'est-ce qui est «trop large» d'une gamme? Un exemple ridicule serait de demander 11 mois de données à partir d'un tableau qui n'a que la valeur d'un an de dossiers. Tirer un jour de données ne devrait pas être un problème, en supposant que vos statistiques soient à jour. (Cependant, l'optimiseur peut avoir des problèmes si vous recherchez les données d'hier et que vous n'avez pas mis à jour les statistiques depuis trois jours.)
Étant donné que vous exécutez une requête "SELECT *", le moteur devra renvoyer toutes les colonnes de la table (même si quelqu'un en ajoute une nouvelle dont votre application n'a pas besoin à ce moment), donc un index de couverture ou un index avec des colonnes incluses n'aidera pas beaucoup, voire pas du tout. (Si vous incluez chaque colonne de la table dans un index, vous faites quelque chose de mal.) L'optimiseur ignorera probablement ces index NC.
Alors que faire?
Ma suggestion serait de supprimer l'index NC, de changer le cluster PK en non cluster et de créer un index cluster sur [DateEntered]. Plus c'est simple, mieux c'est, jusqu'à preuve du contraire.
la source
Tant que vous avez ce "*" là-dedans, alors la seule chose que je pourrais imaginer qui ferait une grande différence serait de changer votre définition d'index en ceci:
Comme je l'ai noté dans les commentaires, il devrait utiliser cet index, mais si ce n'est pas le cas, vous pouvez le persuader avec un ORDER BY ou un indice.
la source
Je regarderais cela un peu différemment.
Je viderais la colonne datetime - la changer en un int. Ayez une table de recherche ou faites une conversion pour votre date.
Vider l'index clusterisé - le laisser comme un tas et créer un index non clusterisé sur la nouvelle colonne INT qui représente la date. c'est-à-dire qu'aujourd'hui serait 20121015. Cet ordre est important. Selon la fréquence à laquelle vous chargez la table, essayez de créer cet index dans l'ordre DESC. Le coût de maintenance sera plus élevé et vous voudrez introduire un facteur de remplissage ou de partitionnement. Le partitionnement aiderait également à réduire votre temps d'exécution.
Enfin, si vous pouvez utiliser SQL 2012, essayez d'utiliser SEQUENCE - il surpassera l'identité () pour les insertions.
la source