Database
 sql >> Base de données >  >> RDS >> Database

Comment les mises à jour automatiques des statistiques peuvent affecter les performances des requêtes

Dans mon article précédent, j'ai exploré différentes méthodes pour suivre les mises à jour automatiques des statistiques afin de déterminer si elles affectaient les performances des requêtes. Dans la seconde moitié du message, j'ai inclus des options, dont l'une consistait à activer le paramètre de base de données de mise à jour automatique des statistiques de manière asynchrone. Dans cet article, je souhaite examiner comment les performances des requêtes changent lorsque la mise à jour automatique se produit avant l'exécution de la requête, et ce qu'il advient des performances si la mise à jour est asynchrone.

La configuration

J'ai commencé avec une copie de la base de données AdventureWorks2012, puis j'ai créé une copie de la table SalesOrderHeader avec plus de 200 millions de lignes à l'aide de ce script. La table a un index clusterisé sur SalesOrderID et un index non clusterisé sur CustomerID, OrderDate, SubTotal. [Remarque :si vous devez effectuer des tests répétés, effectuez une sauvegarde de cette base de données à ce stade pour gagner du temps]. Après avoir chargé les données et créé l'index non clusterisé, j'ai vérifié le nombre de lignes et calculé le nombre de lignes (environ) qu'il faudrait modifier pour invoquer une mise à jour automatique.

SELECT
OBJECT_NAME([p].[object_id]) [TableName],
[si].[name] [IndexName],
[au].[type_desc] [Type],
[p].[rows] [RowCount],
([p].[rows]*.20) + 500 [UpdateThreshold],
[au].total_pages [PageCount],
(([au].[total_pages]*8)/1024)/1024 [TotalGB]
FROM [sys].[partitions] [p]
JOIN [sys].[allocation_units] [au] ON [p].[partition_id] = [au].[container_id]
JOIN [sys].[indexes] [si] on [p].[object_id] = [si].object_id and [p].[index_id] = [si].[index_id]
WHERE [p].[object_id] = OBJECT_ID(N'Sales.Big_SalesOrderHeader');


Informations Big_SalesOrderHeader CIX et NCI

J'ai également vérifié l'en-tête des statistiques actuelles de l'index :

DBCC SHOW_STATISTICS ('Sales.Big_SalesOrderHeader',[IX_Big_SalesOrderHeader_CustomerID_OrderDate_SubTotal]);


Statistiques NCI :Au début

J'ai ensuite créé la procédure stockée que j'utiliserais pour les tests. Il s'agit d'une procédure simple qui interroge Sales.Big_SalesOrderHeader et agrège les données de vente par CustomerID et OrderDate pour analyse :

CREATE PROCEDURE Sales.usp_GetCustomerStats
@CustomerID INT,
@StartDate DATETIME,
@EndDate DATETIME
AS
BEGIN
  SET NOCOUNT ON;
 
  SELECT CustomerID, DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate), COUNT([SalesOrderID]) as Computed
    FROM [Sales].[Big_SalesOrderHeader]
    WHERE CustomerID = @CustomerID
    AND OrderDate BETWEEN @StartDate and @EndDate
    GROUP BY CustomerID, DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate)
    ORDER BY DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate);
END

Enfin, avant d'exécuter la procédure stockée, j'ai créé une session d'événements étendus afin de pouvoir suivre la durée de la requête à l'aide de sp_statement_starting et sp_statement_completed. J'ai également ajouté l'événement auto_stats, car même si je ne m'attendais pas à ce qu'une mise à jour se produise, je voulais utiliser cette même définition de session plus tard.

CREATE EVENT SESSION [StatsUpdate_QueryPerf]
ON SERVER
ADD EVENT sqlserver.auto_stats,
ADD EVENT sqlserver.sp_statement_completed(
SET collect_statement=(1)
),
ADD EVENT sqlserver.sp_statement_starting
ADD TARGET package0.event_file(
SET filename=N'C:\temp\StatsUpdate_QueryPerf.xel'
)
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,
MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF);
GO

L'épreuve

J'ai démarré la session d'événements étendus, puis j'ai exécuté la procédure stockée plusieurs fois, en utilisant différents CustomerID :

ALTER EVENT SESSION [StatsUpdate_QueryPerf]
ON SERVER
STATE = START;
GO
 
EXEC Sales.usp_GetCustomerStats 11331, '2012-08-01 00:00:00.000', '2012-08-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 11506, '2012-11-01 00:00:00.000', '2012-11-30 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 11711, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 15131, '2013-02-01 00:00:00.000', '2013-02-28 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats 15750, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997'
GO

J'ai vérifié le nombre d'exécutions et le plan en interrogeant le cache de procédure :

SELECT
OBJECT_NAME([st].[objectid]),
[st].[text],
[qs].[execution_count],
[qs].[creation_time],
[qs].[last_execution_time],
[qs].[min_worker_time],
[qs].[max_worker_time],
[qs].[min_logical_reads],
[qs].[max_logical_reads],
[qs].[min_elapsed_time],
[qs].[max_elapsed_time],
[qp].[query_plan]
FROM [sys].[dm_exec_query_stats] [qs]
CROSS APPLY [sys].[dm_exec_sql_text]([qs].plan_handle) [st]
CROSS APPLY [sys].[dm_exec_query_plan]([qs].plan_handle) [qp]
WHERE [st].[text] LIKE '%usp_GetCustomerStats%'
AND OBJECT_NAME([st].[objectid]) IS NOT NULL;


Planifier le cache :au début


Plan de requête pour procédure stockée, à l'aide de SQL Sentry Plan Explorer

J'ai pu voir que le plan a été créé au 2014-04-08 18:59:39.850. Avec le plan en cache, j'ai arrêté la session Extended Events :

ALTER EVENT SESSION [StatsUpdate_QueryPerf]
ON SERVER
STATE = STOP;

Ensuite, j'ai ajouté environ 47 millions de lignes de données à la table à l'aide de ce script, bien au-delà du seuil nécessaire pour invalider les statistiques actuelles. Après avoir ajouté les données, j'ai vérifié le nombre de lignes dans le tableau :


CI Big_SalesOrderHeader :après le chargement des données

Avant de relancer ma procédure stockée, j'ai vérifié le cache du plan pour m'assurer que rien n'avait changé et vérifié que les statistiques n'avaient pas encore été mises à jour. N'oubliez pas que même si les statistiques ont été invalidées à ce stade, elles ne seront pas mises à jour tant qu'une requête utilisant la statistique n'aura pas été exécutée (pour référence :Comprendre quand les statistiques seront automatiquement mises à jour). Pour la dernière étape, j'ai redémarré la session d'événements étendus, puis j'ai exécuté la procédure stockée plusieurs fois. Après ces exécutions, j'ai revérifié le cache du plan :


Planifier le cache :après le chargement des données

Le execution_count est à nouveau 8, et si nous regardons le create_time du plan, nous pouvons voir qu'il est changé en 2014-04-08 19:32:52.913. Si nous vérifions le plan, nous pouvons voir que c'est le même, même si le plan a été recompilé :


Plan de requête pour procédure stockée, à l'aide de SQL Sentry Plan Explorer

Analyse de la sortie des événements étendus

J'ai pris le premier fichier d'événements étendus - avant que les données ne soient chargées - et je l'ai ouvert dans SSMS, puis j'ai appliqué un filtre afin que seules les instructions de la procédure stockée soient répertoriées :


Sortie d'événements étendus :après l'exécution initiale du SP

Vous pouvez voir qu'il y a huit (8) exécutions de la procédure stockée, avec des durées de requête qui varient légèrement.

J'ai pris le deuxième fichier d'événements étendus - après le chargement des données - l'ai ouvert SSMS et l'ai filtré à nouveau afin que seules les instructions de la procédure stockée, ainsi que les événements auto_stats, soient répertoriés :


Sortie d'événements étendus :exécution du SP après le chargement des données

La sortie est tronquée, car elle n'est pas entièrement nécessaire pour afficher le résultat principal. Les entrées surlignées en bleu représentent la première exécution de la procédure stockée et notez qu'il y a plusieurs étapes - la mise à jour des statistiques fait partie de l'exécution. L'instruction SELECT démarre (attach_activity_id.seq =3) et les mises à jour des statistiques s'exécutent ensuite. Dans notre exemple, nous avons en fait mis à jour trois statistiques. Une fois la dernière mise à jour terminée (attach_activity_id.seq =11), la procédure stockée démarre et se termine (attach_activity_id.seq =13 et attach_activity_id.seq =14). Il est intéressant de noter qu'il existe un deuxième événement sp_statement_starting pour la procédure stockée (vraisemblablement, le premier est ignoré), de sorte que la durée totale de la procédure stockée est calculée sans la mise à jour des statistiques.

Dans ce scénario, la mise à jour automatique et immédiate des statistiques, c'est-à-dire lorsqu'une requête utilisant des statistiques invalidées s'exécute, entraîne une exécution plus longue de la requête, même si la durée de la requête basée sur l'événement sp_statement_completed est toujours inférieure à 14 000. Le résultat final est qu'il n'est pas bénéfique pour les performances des requêtes, car le plan est exactement le même avant et après la mise à jour des statistiques. Dans ce scénario, le plan de requête et la durée d'exécution ne changent pas après l'ajout de données supplémentaires à la table, de sorte que la mise à jour des statistiques ne fait qu'entraver ses performances. Voyons maintenant ce qui se passe lorsque nous activons l'option Mise à jour automatique des statistiques de manière asynchrone.

Le test, version 2

Nous commençons par restaurer la sauvegarde que j'ai effectuée avant de commencer le premier test. J'ai recréé la procédure stockée, puis modifié l'option de base de données pour mettre à jour les statistiques de manière asynchrone :

USE [master];
GO
ALTER DATABASE [AdventureWorks2012_Big] SET AUTO_UPDATE_STATISTICS_ASYNC ON WITH NO_WAIT
GO

J'ai démarré la session d'événements étendus et exécuté à nouveau la procédure stockée plusieurs fois, en utilisant différents CustomerID :

ALTER EVENT SESSION [StatsUpdate_QueryPerf]
ON SERVER
STATE = START;
GO
 
EXEC Sales.usp_GetCustomerStats11331, '2012-08-01 00:00:00.000', '2012-08-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats11506, '2012-11-01 00:00:00.000', '2012-11-30 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats11711, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats15131, '2013-02-01 00:00:00.000', '2013-02-28 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997'
GO
EXEC Sales.usp_GetCustomerStats15750, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997'
GO

J'ai vérifié le nombre d'exécutions et le plan en interrogeant le cache de procédure :


Planifier le cache :au début, test 2


Plan de requête pour procédure stockée, à l'aide de SQL Sentry Plan Explorer

Pour ce test, le plan a été créé au 2014-04-08 21:15:55.490. J'ai arrêté la session d'événements étendus et ajouté à nouveau environ 47 millions de lignes de données à la table, en utilisant la même requête qu'auparavant.

Une fois les données ajoutées, j'ai vérifié le cache du plan pour m'assurer que rien n'avait changé, et vérifié que les statistiques n'avaient pas encore été mises à jour. Enfin, j'ai redémarré la session d'événements étendus, puis j'ai exécuté la procédure stockée huit fois de plus. Un dernier coup d'œil dans le cache du plan a montré execution_count à 16 et un create_time de 2014-04-08 21:15:55.490. Le execution_count et le create_time démontrent que les statistiques n'ont pas été mises à jour, car le plan n'a pas encore été vidé du cache (si c'était le cas, nous aurions un create_time ultérieur et un execution_count de 8).


Planifier le cache :après le chargement des données, test 2

Si nous ouvrons la sortie des événements étendus après le chargement des données dans SSMS, et que nous filtrons à nouveau pour ne voir que les instructions de la procédure stockée, ainsi que les événements auto_stats, nous trouvons ceci (notez que la sortie est divisée en deux captures d'écran) :


Sortie d'événements étendus :test 2, exécution du SP après le chargement des données, première partie


Sortie d'événements étendus :test 2, exécution du SP après le chargement des données, partie II

Les événements pour l'exécution du premier appel de la procédure stockée sont surlignés en bleu - ils commencent à 2014-04-08 21:54:14.9480607 et il y a sept (7) événements. Notez qu'il existe trois (3) événements auto_stats, mais qu'aucun d'entre eux ne se termine réellement, comme nous l'avons vu lorsque l'option de mise à jour automatique des statistiques de manière asynchrone a été désactivée. Vous remarquerez que la mise à jour automatique démarre presque immédiatement pour l'une des statistiques (2014-04-08 21:54:14.9481288), et ses trois événements ont le texte rouge "Stat Update #1" à côté d'eux. Cette mise à jour des statistiques se termine au 2014-04-08 21:54:16.5392219, un peu moins de deux secondes après son démarrage, mais après la fin de toutes les autres exécutions de la procédure. C'est pourquoi le execution_count de sys.dm_exec_query_stats affiche 16. À partir de la sortie XE, nous pouvons voir que les autres mises à jour de statistiques se terminent ensuite (Stat Update #2 et Stat Update #3). Toutes les mises à jour sont asynchrones à l'exécution de la procédure stockée initiale.

Résumé

Comme vous pouvez le constater, les mises à jour automatiques des statistiques peuvent affecter négativement les performances des requêtes. Le degré d'impact dépendra de la quantité de données à lire pour mettre à jour la statistique et des ressources système. Dans certains cas, les performances des requêtes n'augmentent que de quelques millisecondes et sont très probablement imperceptibles pour les utilisateurs. D'autres fois, la durée peut augmenter considérablement, ce qui affecte alors l'expérience de l'utilisateur final. Dans le cas où le plan de requête ne change pas après une mise à jour des statistiques, il vaut la peine d'envisager d'activer l'option de mise à jour automatique des statistiques de manière asynchrone, pour atténuer l'impact sur les performances des requêtes.