J'ai créé la table big_table selon votre schéma
create table big_table
(
updatetime datetime not null,
name char(14) not null,
TheData float,
primary key(Name,updatetime)
)
J'ai ensuite rempli le tableau avec 50 000 lignes avec ce code:
DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
set @rownum = @ROWNUM + 1
insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
if @ROWNUM > 50000
BREAK;
END
À l'aide de SSMS, j'ai ensuite testé les deux requêtes et réalisé que dans la première requête, vous recherchez le MAX de TheData et dans la seconde, le MAX de la mise à jour
J'ai donc modifié la première requête pour obtenir également le MAX de mise à jour
set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)
-- query 1
SELECT MAX([UpdateTime])
FROM big_table
-- query 2
SELECT MAX([UpdateTime]) AS value
from
(
SELECT [UpdateTime]
FROM big_table
group by [UpdateTime]
) as t
set statistics time off
set statistics io off
En utilisant Statistics Time, je récupère le nombre de millisecondes nécessaires pour analyser, compiler et exécuter chaque instruction
En utilisant Statistics IO, je récupère des informations sur l'activité du disque
STATISTICS TIME et STATISTICS IO fournissent des informations utiles. Tels que les tables temporaires utilisées (indiquées par la table de travail). Le nombre de pages logiques lues a également été lu, ce qui indique le nombre de pages de base de données lues dans le cache.
J'active ensuite le plan d'exécution avec CTRL + M (active afficher le plan d'exécution réel), puis j'exécute avec F5.
Cela fournira une comparaison des deux requêtes.
Voici la sortie de l' onglet Messages
- Requête 1
Table 'big_table'. Nombre de balayages 1, lectures logiques 543 , lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Temps d'exécution SQL Server:
temps CPU = 16 ms, temps écoulé = 6 ms .
- Requête 2
Table ' table de travail . Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Table 'big_table'. Nombre de balayages 1, lectures logiques 543 , lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Temps d'exécution SQL Server:
temps CPU = 0 ms, temps écoulé = 35 ms .
Les deux requêtes entraînent 543 lectures logiques, mais la deuxième requête a un temps écoulé de 35 ms alors que la première n'a que 6 ms. Vous remarquerez également que la deuxième requête entraîne l'utilisation de tables temporaires dans tempdb, indiquées par le mot table de travail . Même si toutes les valeurs de la table de travail sont à 0, le travail a toujours été effectué dans tempdb.
Ensuite, il y a la sortie de l' onglet Plan d'exécution réel à côté de l'onglet Messages
Selon le plan d'exécution fourni par MSSQL, la deuxième requête que vous avez fournie a un coût total par lot de 64% tandis que la première ne coûte que 36% du lot total, donc la première requête nécessite moins de travail.
À l'aide de SSMS, vous pouvez tester et comparer vos requêtes et savoir exactement comment MSSQL analyse vos requêtes et quels objets: tables, index et / ou statistiques, le cas échéant, sont utilisés pour satisfaire ces requêtes.
Une remarque supplémentaire à garder à l'esprit lors du test consiste à nettoyer le cache avant le test, si possible. Cela permet de garantir l'exactitude des comparaisons, ce qui est important lorsque vous pensez à l'activité du disque. Je commence par DBCC DROPCLEANBUFFERS et DBCC FREEPROCCACHE pour vider tout le cache. Attention cependant à ne pas utiliser ces commandes sur un serveur de production réellement utilisé car vous forcerez effectivement le serveur à tout lire du disque dans la mémoire.
Voici la documentation pertinente.
- Vider le cache du plan avec DBCC FREEPROCCACHE
- Effacez tout du pool de tampons avec DBCC DROPCLEANBUFFERS
L'utilisation de ces commandes peut ne pas être possible selon la façon dont votre environnement est utilisé.
Mis à jour 10/28 12:46 pm
Correction de l'image du plan d'exécution et de la sortie des statistiques.
getdate()
sortir de la boucleLa réécriture aurait peut-être aidé si SQL Server avait implémenté le saut d'index, mais ce n'est pas le cas.
Le saut d'index permet à un moteur de base de données de rechercher la prochaine valeur d'index différente au lieu d'analyser tous les doublons (ou sous-clés non pertinents) entre les deux. Dans votre cas, skip-scan permettrait au moteur de trouver
MAX(UpdateTime)
le premierName
, de passer auMAX(UpdateTime)
secondName
... et ainsi de suite. La dernière étape serait de trouver lesMAX(UpdateTime)
candidats un par nom.Vous pouvez simuler cela dans une certaine mesure en utilisant un CTE récursif, mais c'est un peu désordonné, et pas aussi efficace que le skip-scan intégré serait:
Ce plan effectue une recherche de singleton pour chaque distinct
Name
, puis trouve le plus élevéUpdateTime
parmi les candidats. Ses performances par rapport à une simple analyse complète du tableau dépendent du nombre de doublons parName
et du fait que les pages touchées par le singleton soient en mémoire ou non.Solutions alternatives
Si vous pouvez créer un nouvel index sur cette table, un bon choix pour cette requête serait un index
UpdateTime
seul:Cet index permettra au moteur d'exécution de trouver le plus haut
UpdateTime
avec une recherche singleton à la fin de l'arbre b de l'index:Ce plan ne consomme que quelques E / S logiques (pour naviguer dans les niveaux de l'arborescence b) et se termine immédiatement. Notez que l'analyse d'index dans le plan n'est pas une analyse complète du nouvel index - elle renvoie simplement une ligne à partir de la «fin» de l'index.
Si vous ne souhaitez pas créer un nouvel index complet sur la table, vous pouvez envisager une vue indexée contenant uniquement les
UpdateTime
valeurs uniques :Cela a l'avantage de créer uniquement une structure avec autant de lignes qu'il y a de
UpdateTime
valeurs uniques , bien que chaque requête qui modifie des données dans la table de base aura des opérateurs supplémentaires ajoutés à son plan d'exécution pour maintenir la vue indexée. La requête pour trouver laUpdateTime
valeur maximale serait:la source