Superviser les performances de Azure SQL Database avec des vues de gestion dynamique

S’applique à : Azure SQL Database

Vous pouvez utiliser les vues de gestion dynamique (DMV) pour surveiller les performances de la charge de travail et diagnostiquer les problèmes de performances qui peuvent être causés par des requêtes bloquées ou longues, des goulots d’étranglement des ressources, des plans de requête non optimaux, et bien plus encore.

Cet article fournit des informations sur la façon de détecter des problèmes de performances courants en interrogeant des vues de gestion dynamique par le biais de T-SQL. Vous pouvez utiliser n’importe quel outil de requête, par exemple :

autorisations

Dans Azure SQL Database, en fonction de l’option de taille de calcul, de l’option de déploiement et des données dans la DMV, l’interrogation d’une DMV peut nécessiter l’autorisation VIEW DATABASE STATE, VIEW SERVER PERFORMANCE STATE ou VIEW SERVER SECURITY STATE. Les deux dernières autorisations sont incluses dans l’autorisation VIEW SERVER STATE. Les autorisations d’affichage de l’état du serveur sont accordées via l’abonnement aux rôles serveur correspondants. Pour déterminer les autorisations requises pour interroger une DMV spécifique, consultez Vues de gestion dynamique et recherchez l’article décrivant la DMV.

Pour accorder l’autorisation VIEW DATABASE STATE à un utilisateur de base de données, exécutez la requête suivante, en remplaçant database_user par le nom du principal d’utilisateur dans la base de données :

GRANT VIEW DATABASE STATE TO [database_user];

Pour accorder l’appartenance au rôle serveur ##MS_ServerStateReader## à un identifiant nommé login_name sur un serveur logique, connectez-vous à la base de données master, puis exécutez par exemple la requête suivante :

ALTER SERVER ROLE [##MS_ServerStateReader##] ADD MEMBER [login_name];

La prise en compte de l’autorisation accordée peut prendre quelques minutes. Pour plus d’informations, consultez Limitations des rôles de niveau serveur.

Surveiller l’utilisation des ressources

Vous pouvez surveiller l’utilisation des ressources au niveau de la base de données à l’aide des vues suivantes. Ces vues s’appliquent aux bases de données autonomes et aux bases de données dans un pool élastique.

Vous pouvez surveiller l’utilisation des ressources au niveau du pool élastique à l’aide des vues suivantes :

Vous pouvez surveiller l’utilisation des ressources au niveau de la requête en utilisant l’analyse des performances des requêtes SQL Database dans le portail Azure ou le Magasin des requêtes.

sys.dm_db_resource_stats

Vous pouvez utiliser la vue sys.dm_db_resource_stats dans chaque base de données. La vue sys.dm_db_resource_stats affiche les données récentes d’utilisation des ressources par rapport aux limites de la taille de calcul. Les pourcentages d’UC, d’E/S de données, d’écritures dans les journaux, de threads de travail et d’utilisation de la mémoire par rapport à la limite sont enregistrés pour chaque intervalle de 15 secondes et sont conservés pendant environ une heure.

Étant donné que cette vue fournit des données granulaires sur l’utilisation des ressources, utilisez d’abord sys.dm_db_resource_stats pour n’importe quelle analyse d’état actuel ou pour la résolution des problèmes. Par exemple, cette requête affiche l’utilisation moyenne et maximale des ressources pour la base de données actuelle sur la dernière heure :

SELECT
    database_name = DB_NAME(),
    AVG(avg_cpu_percent) AS 'Average CPU use in percent',
    MAX(avg_cpu_percent) AS 'Maximum CPU use in percent',
    AVG(avg_data_io_percent) AS 'Average data IO in percent',
    MAX(avg_data_io_percent) AS 'Maximum data IO in percent',
    AVG(avg_log_write_percent) AS 'Average log write use in percent',
    MAX(avg_log_write_percent) AS 'Maximum log write use in percent',
    AVG(avg_memory_usage_percent) AS 'Average memory use in percent',
    MAX(avg_memory_usage_percent) AS 'Maximum memory use in percent',
    MAX(max_worker_percent) AS 'Maximum worker use in percent'
FROM sys.dm_db_resource_stats

Pour les autres requêtes, consultez les exemples dans sys.dm_db_resource_stats.

sys.resource_stats

La vue sys.resource_stats de la base de données master fournit des informations supplémentaires vous permettant de superviser les performances de votre base de données par rapport à son niveau de service et à sa taille de calcul. Les données, qui sont collectées toutes les cinq minutes, sont conservées pendant environ 14 jours. Cette vue est utile pour une analyse historique de plus long terme sur l’utilisation des ressources par votre base de données.

Le graphique suivant illustre l’utilisation des ressources d’UC pour une base de données Premium avec la taille de calcul P2 pour chaque heure de la semaine. Ce graphique spécifique commence un lundi, affiche cinq journées de travail, puis un week-end où l’application connaît une activité réduite.

Capture d’écran d’un exemple de graphique d’utilisation de ressources de base de données.

D’après les données, cette base de données présente actuellement une charge d’UC maximale légèrement supérieure à 50 % de l’utilisation de l’UC, par rapport à la taille de calcul P2 (le mardi, à la mi-journée). Si le processeur est le facteur dominant dans le profil de ressource de l’application, vous pouvez décider que P2 est la taille de calcul permettant de garantir que la charge de travail est toujours adaptée. Si vous prévoyez une croissance de l’application au fil du temps, vous avez tout intérêt à avoir une mémoire-tampon de ressources supplémentaires, afin que l’application n’atteigne jamais le plafond de performances. En augmentant la taille de calcul, vous pouvez éviter les erreurs visibles par les clients, qui sont susceptibles de se produire lorsqu’une application ne dispose pas de la puissance nécessaire pour traiter efficacement les requêtes, plus particulièrement dans les environnements sensibles à la latence.

Vous pouvez interpréter différemment le même graphique pour d’autres types d’applications. Par exemple, si une application essaie de traiter les données de paie chaque jour et obtient le même graphique, ce genre de modèle de « traitement par lot » peut convenir avec une taille de calcul P1. La taille de calcul P1 comprend 100 DTU, contre 200 DTU pour la taille de calcul P2. La taille de calcul P1 fournit la moitié des performances de la taille de calcul P2. Par conséquent, 50 % d’utilisation de l’UC au niveau P2 correspond à 100 % d’utilisation de l’UC au niveau de performance P1. Si l’application n’a pas de délai d’expiration, le fait qu’une tâche volumineuse s’exécute en 2 h ou 2 h 30 peut ne pas avoir d’importance à condition qu’elle soit effectuée le jour même. Une application de cette catégorie peut probablement utiliser une taille de calcul P1. Vous pouvez tirer parti du fait qu’il y a des périodes pendant la journée où l’utilisation des ressources est moindre, ce qui signifie que toute période de pointe peut déborder sur l’un des creux plus tard dans la journée. La taille de calcul P1 peut convenir pour une application de ce type (et permettre de réaliser des économies) tant que les travaux peuvent se terminer à temps chaque jour.

Le moteur de base de données expose les informations sur les ressources utilisées pour chaque base de données active dans la vue sys.resource_stats de la base de données master sur chaque serveur logique. Les données de la vue sont agrégées par intervalle de 5 minutes. Les données peuvent prendre plusieurs minutes pour apparaître dans la table ; sys.resource_stats convient donc mieux aux analyses historiques qu’aux analyses en quasi-temps réel. Interrogez la vue sys.resource_stats pour voir l’historique récent d’une base de données et valider que la taille de calcul sélectionnée a fourni les performances souhaitées lorsque c’était nécessaire.

Remarque

Vous devez être connecté à la base de données master pour interroger sys.resource_stats dans les exemples suivants.

Cet exemple montre les données dans sys.resource_stats :

SELECT TOP 10 *
FROM sys.resource_stats
WHERE database_name = 'userdb1'
ORDER BY start_time DESC;

L’exemple suivant vous montre différentes manières d’utiliser la vue du catalogue sys.resource_stats pour obtenir des informations sur la façon dont votre base de données utilise les ressources :

  1. Pour examiner l’utilisation des ressources de la semaine dernière pour la base de données utilisateur userdb1, vous pouvez exécuter cette requête en substituant votre propre nom de base de données :

    SELECT *
    FROM sys.resource_stats
    WHERE database_name = 'userdb1' 
        AND start_time > DATEADD(day, -7, GETDATE())
    ORDER BY start_time DESC;
    
  2. Pour évaluer l’adéquation entre votre charge de travail et la taille de calcul, vous devez étudier les différents aspects des métriques de ressources : UC, entrées/sorties de données, nombre de Workers et nombre de sessions. Voici une requête révisée utilisant sys.resource_stats pour signaler les valeurs moyennes et maximales de ces métriques de ressources, pour chaque taille de calcul pour laquelle la base de données a été provisionnée :

    SELECT rs.database_name
    ,    rs.sku
    ,    storage_mb                           = MAX(rs.storage_in_megabytes)
    ,    'Average CPU Utilization In %'       = AVG(rs.avg_cpu_percent)
    ,    'Maximum CPU Utilization In %'       = MAX(rs.avg_cpu_percent)
    ,    'Average Data IO In %'               = AVG(rs.avg_data_io_percent)
    ,    'Maximum Data IO In %'               = MAX(rs.avg_data_io_percent)
    ,    'Average Log Write Utilization In %' = AVG(rs.avg_log_write_percent)
    ,    'Maximum Log Write Utilization In %' = MAX(rs.avg_log_write_percent)
    ,    'Maximum Requests In %'              = MAX(rs.max_worker_percent)
    ,    'Maximum Sessions In %'              = MAX(rs.max_session_percent)
    FROM sys.resource_stats AS rs
    WHERE rs.database_name = 'userdb1' 
    AND rs.start_time > DATEADD(day, -7, GETDATE())
    GROUP BY rs.database_name, rs.sku;
    
  3. Avec les informations ci-dessus relatives aux valeurs moyennes et maximales de chaque métrique de ressources, vous pouvez évaluer l’adéquation entre votre charge de travail et la taille de calcul que vous avez choisie. En général, les valeurs moyennes de sys.resource_stats vous offrent une bonne référence à utiliser par rapport à la taille cible.

    • Pour les bases de données de modèle d’achat de DTU :

      Par exemple, vous pouvez utiliser le niveau de service Standard avec la taille de calcul S2. Les pourcentages d’utilisation moyens pour les lectures et écritures d’UC et d’E/S se situent en deçà de 40 %, le nombre moyen de Workers est inférieur à 50 et le nombre moyens de sessions est inférieur à 200. Votre charge de travail peut être prise en charge par la taille de calcul S1. Il est facile de voir si votre base de données s’intègre dans les limites de Workers et de sessions. Pour voir si une base de données peut s’adapter à une taille de calcul inférieure, divisez le nombre de DTU de la taille de calcul inférieure par le nombre de DTU de votre taille de calcul actuelle, puis multipliez le résultat par 100 :

      S1 DTU / S2 DTU * 100 = 20 / 50 * 100 = 40

      Le résultat correspond à la différence de performances relative entre les deux tailles de calcul en pourcentage. Si votre utilisation des ressources ne dépasse pas ce pourcentage, votre charge de travail peut être traitée par la taille de calcul inférieure. Toutefois, vous devez également examiner toutes les plages de valeurs d’utilisation des ressources, et déterminer, d’après le pourcentage, combien de fois la charge de travail de votre base de données pourrait s’adapter à la taille de calcul inférieure. La requête suivante génère le pourcentage d’adéquation par dimension de ressource, selon le seuil de 40 % calculé dans cet exemple :

       SELECT database_name,
           100*((COUNT(database_name) - SUM(CASE WHEN avg_cpu_percent >= 40 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'CPU Fit Percent',
           100*((COUNT(database_name) - SUM(CASE WHEN avg_log_write_percent >= 40 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'Log Write Fit Percent',
           100*((COUNT(database_name) - SUM(CASE WHEN avg_data_io_percent >= 40 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'Physical Data IO Fit Percent'
       FROM sys.resource_stats
       WHERE start_time > DATEADD(day, -7, GETDATE())
       AND database_name = 'sample' --remove to see all databases
       GROUP BY database_name;
      

      En fonction du niveau de service de votre base de données, vous pouvez décider si votre charge de travail peut s’adapter à la taille de calcul inférieure. Si l’objectif de charge de travail de votre base de données est de 99,9 % et que la requête précédente retourne des valeurs supérieures à 99,9 % pour les trois dimensions de ressources, il est probable que votre charge de travail puisse s’adapter à la taille de calcul inférieure.

      En examinant le pourcentage d’adéquation, vous savez par ailleurs si vous devez passer à la taille de calcul supérieure pour atteindre votre objectif. Par exemple, l’utilisation du processeur pour un exemple de base de données au cours de la dernière semaine :

      Pourcentage moyen d’UC Pourcentage maximum d’UC
      24,5 100,00

      Le pourcentage moyen d’UC représente environ un quart de la limite de la taille de calcul, ce qui correspondrait bien à la taille de calcul de la base de données.

    • Pour les bases de données de modèle d’achat de DTU et de modèle d’achat de vCore :

      La valeur maximale montre que la base de données atteint la limite de la taille de calcul. Devez-vous passer à la taille de calcul supérieure ? Regardez le nombre de fois où votre charge de travail atteint 100 % et comparez ce chiffre à l’objectif de charge de travail de votre base de données.

       SELECT database_name,
           100*((COUNT(database_name) - SUM(CASE WHEN avg_cpu_percent >= 100 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'CPU Fit Percent',
           100*((COUNT(database_name) - SUM(CASE WHEN avg_log_write_percent >= 100 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'Log Write Fit Percent',
           100*((COUNT(database_name) - SUM(CASE WHEN avg_data_io_percent >= 100 THEN 1 ELSE 0 END) * 1.0) / COUNT(database_name)) AS 'Physical Data IO Fit Percent'
       FROM sys.resource_stats
       WHERE start_time > DATEADD(day, -7, GETDATE())
       AND database_name = 'sample'  --remove to see all databases
       GROUP BY database_name;
      

      Ces pourcentages correspondent au nombre d’échantillons que votre charge de travail fait rentrer sous la taille de calcul actuelle. Si cette requête retourne une valeur inférieure à 99,9 % pour l’une des trois dimensions de ressources, votre charge de travail moyenne échantillonnée a dépassé les limites. Passez à la taille de calcul supérieure ou utilisez des techniques de réglage d’application afin de réduire la charge sur la base de données.

sys.dm_elastic_pool_resource_stats

De la même manière que sys.dm_db_resource_stats, sys.dm_elastic_pool_resource_stats fournit des données récentes et granulaires d’utilisation des ressources pour un pool élastique. La vue peut être interrogée dans n’importe quelle base de données d’un pool élastique afin de fournir des données d’utilisation des ressources pour un pool entier, plutôt que pour une base de données spécifique. Les valeurs de pourcentage signalées par cette DMV sont en rapport aux limites du pool élastique, qui peuvent être supérieures aux limites d’une base de données dans le pool.

Cet exemple montre les données d’utilisation des ressources résumées pour le pool élastique actuel au cours des 15 dernières minutes :

SELECT dso.elastic_pool_name,
       AVG(eprs.avg_cpu_percent) AS avg_cpu_percent,
       MAX(eprs.avg_cpu_percent) AS max_cpu_percent,
       AVG(eprs.avg_data_io_percent) AS avg_data_io_percent,
       MAX(eprs.avg_data_io_percent) AS max_data_io_percent,
       AVG(eprs.avg_log_write_percent) AS avg_log_write_percent,
       MAX(eprs.avg_log_write_percent) AS max_log_write_percent,
       MAX(eprs.max_worker_percent) AS max_worker_percent,
       MAX(eprs.used_storage_percent) AS max_used_storage_percent,
       MAX(eprs.allocated_storage_percent) AS max_allocated_storage_percent
FROM sys.dm_elastic_pool_resource_stats AS eprs
CROSS JOIN sys.database_service_objectives AS dso
WHERE eprs.end_time >= DATEADD(minute, -15, GETUTCDATE())
GROUP BY dso.elastic_pool_name;

Si vous constatez qu’une utilisation des ressources approche les 100 % pendant une période importante, vous devrez peut-être examiner l’utilisation des ressources pour les bases de données individuelles dans le même pool élastique afin de déterminer la contribution de chaque base de données à l’utilisation des ressources au niveau du pool.

sys.elastic_pool_resource_stats

De même que sys.resource_stats, sys.elastic_pool_resource_stats dans la base de données master fournit des données d’utilisation des ressources historiques pour tous les pools élastiques sur le serveur logique. Vous pouvez utiliser sys.elastic_pool_resource_stats pour la surveillance historique au cours des 14 derniers jours, y compris l’analyse des tendances d’utilisation.

Cet exemple montre les données d’utilisation des ressources résumées au cours des sept derniers jours pour tous les pools élastiques sur le serveur logique actuel. Exécutez la requête dans la base de données master.

SELECT elastic_pool_name,
       AVG(avg_cpu_percent) AS avg_cpu_percent,
       MAX(avg_cpu_percent) AS max_cpu_percent,
       AVG(avg_data_io_percent) AS avg_data_io_percent,
       MAX(avg_data_io_percent) AS max_data_io_percent,
       AVG(avg_log_write_percent) AS avg_log_write_percent,
       MAX(avg_log_write_percent) AS max_log_write_percent,
       MAX(max_worker_percent) AS max_worker_percent,
       AVG(avg_storage_percent) AS avg_used_storage_percent,
       MAX(avg_storage_percent) AS max_used_storage_percent,
       AVG(avg_allocated_storage_percent) AS avg_allocated_storage_percent,
       MAX(avg_allocated_storage_percent) AS max_allocated_storage_percent
FROM sys.elastic_pool_resource_stats
WHERE start_time >= DATEADD(day, -7, GETUTCDATE())
GROUP BY elastic_pool_name
ORDER BY elastic_pool_name ASC;

Demandes simultanées

Pour afficher le nombre actuel de requêtes simultanées, exécutez cette requête sur votre base de données utilisateur :

SELECT COUNT(*) AS [Concurrent_Requests]
FROM sys.dm_exec_requests;

Il s’agit simplement d’un instantané à un point unique dans le temps. Pour obtenir une meilleure compréhension de votre charge de travail et des exigences liées aux demandes simultanées, il vous faut collecter plusieurs échantillons au fil du temps.

Taux moyen de requête

Cet exemple montre comment trouver le taux de requête moyen pour une base de données ou pour les bases de données d’un pool élastique sur une période donnée. Dans cet exemple, la période est définie sur 30 secondes. Vous pouvez l’ajuster en modifiant l’instruction WAITFOR DELAY. Exécutez cette requête dans votre base de données utilisateur. Si la base de données se trouve dans un pool élastique et que vous disposez d’autorisations suffisantes, les résultats incluent d’autres bases de données dans le pool élastique.

DECLARE @DbRequestSnapshot TABLE (
                                 database_name sysname PRIMARY KEY,
                                 total_request_count bigint NOT NULL,
                                 snapshot_time datetime2 NOT NULL DEFAULT (SYSDATETIME())
                                 );

INSERT INTO @DbRequestSnapshot
(
database_name,
total_request_count
)
SELECT rg.database_name,
       wg.total_request_count
FROM sys.dm_resource_governor_workload_groups AS wg
INNER JOIN sys.dm_user_db_resource_governance AS rg
ON wg.name = CONCAT('UserPrimaryGroup.DBId', rg.database_id);

WAITFOR DELAY '00:00:30';

SELECT rg.database_name,
       (wg.total_request_count - drs.total_request_count) / DATEDIFF(second, drs.snapshot_time, SYSDATETIME()) AS requests_per_second
FROM sys.dm_resource_governor_workload_groups AS wg
INNER JOIN sys.dm_user_db_resource_governance AS rg
ON wg.name = CONCAT('UserPrimaryGroup.DBId', rg.database_id)
INNER JOIN @DbRequestSnapshot AS drs
ON rg.database_name = drs.database_name;

Sessions actuelles

Pour voir le nombre de sessions simultanément actives, exécutez cette requête sur votre base de données :

SELECT COUNT(*) AS [Sessions]
FROM sys.dm_exec_sessions
WHERE is_user_process = 1;

Cette requête retourne un décompte ponctuel. Si vous collectez plusieurs échantillons au fur et à mesure, vous bénéficierez d’une meilleure compréhension de l’utilisation de votre session.

Historique récent des requêtes, des sessions et des workers

Cet exemple retourne l’utilisation historique récente des requêtes, des sessions et des threads de travail pour une base de données ou pour les bases de données d’un pool élastique. Chaque ligne représente un instantané de l’utilisation des ressources à un moment donné pour une base de données. La colonne requests_per_second est le taux de requête moyen pendant l’intervalle de temps qui se termine à snapshot_time. Si la base de données se trouve dans un pool élastique et que vous disposez d’autorisations suffisantes, les résultats incluent d’autres bases de données dans le pool élastique.

SELECT rg.database_name,
       wg.snapshot_time,
       wg.active_request_count,
       wg.active_worker_count,
       wg.active_session_count,
       CAST(wg.delta_request_count AS decimal) / duration_ms * 1000 AS requests_per_second
FROM sys.dm_resource_governor_workload_groups_history_ex AS wg
INNER JOIN sys.dm_user_db_resource_governance AS rg
ON wg.name = CONCAT('UserPrimaryGroup.DBId', rg.database_id)
ORDER BY snapshot_time DESC;

Calculer les tailles de base de données et d’objets

La requête suivante renvoie la taille des données de votre base de données (en mégaoctets) :

-- Calculates the size of the database.
SELECT SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS bigint) * 8192.) / 1024 / 1024 AS size_mb
FROM sys.database_files
WHERE type_desc = 'ROWS';

La requête suivante retourne la taille d’objets individuels (en mégaoctets) dans votre base de données :

-- Calculates the size of individual database objects.
SELECT o.name, SUM(ps.reserved_page_count) * 8.0 / 1024 AS size_mb
FROM sys.dm_db_partition_stats AS ps 
    INNER JOIN sys.objects AS o 
        ON ps.object_id = o.object_id
GROUP BY o.name
ORDER BY size_mb DESC;

Identifier les problèmes de performances d’UC

Cette section vous aide à identifier les requêtes individuelles qui consomment le plus d’UC.

Si la consommation du processeur est supérieure à 80 % pendant de longues périodes, envisagez les étapes de résolution des problèmes suivantes, que le problème de processeur se produise maintenant ou se soit produit dans le passé. Vous pouvez également suivre les étapes de cette section pour identifier de manière proactive les principales requêtes consommatrices d’UC et les ajuster. Dans certains cas, la réduction de la consommation d’UC peut vous permettre d’effectuer un scale-down de vos bases de données et des pools élastiques et d’économiser des coûts.

Les étapes de dépannage sont les mêmes pour les bases de données autonomes et les bases de données dans un pool élastique. Exécutez toutes les requêtes dans la base de données utilisateur.

Le problème de l’UC se produit maintenant

Si le problème se produit maintenant, il y a deux scénarios possibles :

De nombreuses requêtes individuelles consomment une quantité cumulée d’UC élevée

Utilisez la requête suivante pour identifier les principales requêtes par code de hachage de requête :

PRINT '-- top 10 Active CPU Consuming Queries (aggregated)--';
SELECT TOP 10 GETDATE() runtime, *
FROM (SELECT query_stats.query_hash, SUM(query_stats.cpu_time) 'Total_Request_Cpu_Time_Ms', SUM(logical_reads) 'Total_Request_Logical_Reads', MIN(start_time) 'Earliest_Request_start_Time', COUNT(*) 'Number_Of_Requests', SUBSTRING(REPLACE(REPLACE(MIN(query_stats.statement_text), CHAR(10), ' '), CHAR(13), ' '), 1, 256) AS "Statement_Text"
    FROM (SELECT req.*, SUBSTRING(ST.text, (req.statement_start_offset / 2)+1, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(ST.text)ELSE req.statement_end_offset END-req.statement_start_offset)/ 2)+1) AS statement_text
            FROM sys.dm_exec_requests AS req
                CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) AS ST ) AS query_stats
    GROUP BY query_hash) AS t
ORDER BY Total_Request_Cpu_Time_Ms DESC;

Des requêtes longues consommant l’UC sont toujours en cours d’exécution

Utilisez la requête suivante pour identifier ces requêtes :

PRINT '--top 10 Active CPU Consuming Queries by sessions--';
SELECT TOP 10 req.session_id, req.start_time, cpu_time 'cpu_time_ms', OBJECT_NAME(ST.objectid, ST.dbid) 'ObjectName', SUBSTRING(REPLACE(REPLACE(SUBSTRING(ST.text, (req.statement_start_offset / 2)+1, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(ST.text)ELSE req.statement_end_offset END-req.statement_start_offset)/ 2)+1), CHAR(10), ' '), CHAR(13), ' '), 1, 512) AS statement_text
FROM sys.dm_exec_requests AS req
    CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) AS ST
ORDER BY cpu_time DESC;
GO

Le problème de l’UC s’est produit dans le passé

Si le problème s’est produit dans le passé et que vous souhaitez effectuer une analyse de la cause racine, utilisez Magasin de requêtes. Les utilisateurs ayant accès à la base de données peuvent utiliser T-SQL pour interroger les données du Magasin de requêtes. Par défaut, le Magasin des requêtes capture les statistiques de requête agrégées par intervalles d’une heure.

  1. Utilisez la requête suivante pour examiner l’activité des requêtes ayant une consommation d’UC élevée. Cette requête retourne les 15 premières requêtes consommant l’UC. N’oubliez pas de changer rsi.start_time >= DATEADD(hour, -2, GETUTCDATE() pour examiner une période autre que les deux dernières heures :

    -- Top 15 CPU consuming queries by query hash
    -- Note that a query hash can have many query ids if not parameterized or not parameterized properly
    WITH AggregatedCPU
    AS (
        SELECT q.query_hash
            ,SUM(count_executions * avg_cpu_time / 1000.0) AS total_cpu_ms
            ,SUM(count_executions * avg_cpu_time / 1000.0) / SUM(count_executions) AS avg_cpu_ms
            ,MAX(rs.max_cpu_time / 1000.00) AS max_cpu_ms
            ,MAX(max_logical_io_reads) max_logical_reads
            ,COUNT(DISTINCT p.plan_id) AS number_of_distinct_plans
            ,COUNT(DISTINCT p.query_id) AS number_of_distinct_query_ids
            ,SUM(CASE 
                    WHEN rs.execution_type_desc = 'Aborted'
                        THEN count_executions
                    ELSE 0
                    END) AS Aborted_Execution_Count
            ,SUM(CASE 
                    WHEN rs.execution_type_desc = 'Regular'
                        THEN count_executions
                    ELSE 0
                    END) AS Regular_Execution_Count
            ,SUM(CASE 
                    WHEN rs.execution_type_desc = 'Exception'
                        THEN count_executions
                    ELSE 0
                    END) AS Exception_Execution_Count
            ,SUM(count_executions) AS total_executions
            ,MIN(qt.query_sql_text) AS sampled_query_text
        FROM sys.query_store_query_text AS qt
            INNER JOIN sys.query_store_query AS q ON qt.query_text_id = q.query_text_id
            INNER JOIN sys.query_store_plan AS p ON q.query_id = p.query_id
            INNER JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id = p.plan_id
            INNER JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
        WHERE rs.execution_type_desc IN ('Regular','Aborted','Exception')
            AND rsi.start_time >= DATEADD(HOUR, - 2, GETUTCDATE())
        GROUP BY q.query_hash
        )
        ,OrderedCPU
    AS (
        SELECT query_hash
            ,total_cpu_ms
            ,avg_cpu_ms
            ,max_cpu_ms
            ,max_logical_reads
            ,number_of_distinct_plans
            ,number_of_distinct_query_ids
            ,total_executions
            ,Aborted_Execution_Count
            ,Regular_Execution_Count
            ,Exception_Execution_Count
            ,sampled_query_text
            ,ROW_NUMBER() OVER (
                ORDER BY total_cpu_ms DESC
                    ,query_hash ASC
                ) AS query_hash_row_number
        FROM AggregatedCPU
        )
    SELECT OD.query_hash
        ,OD.total_cpu_ms
        ,OD.avg_cpu_ms
        ,OD.max_cpu_ms
        ,OD.max_logical_reads
        ,OD.number_of_distinct_plans
        ,OD.number_of_distinct_query_ids
        ,OD.total_executions
        ,OD.Aborted_Execution_Count
        ,OD.Regular_Execution_Count
        ,OD.Exception_Execution_Count
        ,OD.sampled_query_text
        ,OD.query_hash_row_number
    FROM OrderedCPU AS OD
    WHERE OD.query_hash_row_number <= 15 --get top 15 rows by total_cpu_ms
    ORDER BY total_cpu_ms DESC;
    
  2. Une fois que vous avez identifié les requêtes problématiques, vous devez les paramétrer pour réduire leur consommation d’UC. Vous pouvez également choisir d’augmenter la taille de calcul de la base de données ou du pool élastique pour contourner le problème.

Pour plus d’informations sur la gestion des problèmes de performances du processeur dans Azure SQL Database, consultez Diagnostiquer et résoudre des problèmes liés à l’utilisation élevée du processeur dans Azure SQL Database.

Identifier les problèmes de performances d’E/S

Lors de l’identification des problèmes de performances d’entrée/sortie (E/S) de stockage, les principaux types d’attente sont les suivants :

  • PAGEIOLATCH_*

    Pour les problèmes d’E/S de fichier de données (y compris PAGEIOLATCH_SH, PAGEIOLATCH_EX, PAGEIOLATCH_UP). Si le nom du type d’attente contient IO, cela indique un problème d’E/S. Si le nom d’attente de verrou de la page ne comporte pas IO, cela indique un problème différent qui n’est pas lié aux performances du stockage (par exemple, contention de tempdb).

  • WRITE_LOG

    Pour les problèmes d’E/S liés au journal des transactions.

Si le problème d’E/S se produit maintenant

Utilisez sys.dm_exec_requests ou sys.dm_os_waiting_tasks pour voir les valeurs wait_type et wait_time.

Identifier l’utilisation des données et des E/S de journal

Utilisez la requête suivante pour identifier l’utilisation des données et des E/S de journal.

SELECT
    database_name = DB_NAME()
,   UTC_time = end_time
,   'Data IO In % of Limit'                   = rs.avg_data_io_percent
,   'Log Write Utilization In % of Limit'     = rs.avg_log_write_percent
FROM sys.dm_db_resource_stats AS rs  --past hour only
ORDER BY  rs.end_time DESC;

Pour plus d’exemples d’utilisation de sys.dm_db_resource_stats, consultez la section Superviser l’utilisation des ressources plus loin dans cet article.

Si la limite d’E/S a été atteinte, vous avez deux options :

  • mise à niveau de la taille de calcul ou du niveau de service
  • identifier et régler les requêtes qui consomment le plus d’E/S.

Pour identifier les principales requêtes par temps d’attente liés aux E/S, vous pouvez utiliser la requête suivante sur le Magasin des requêtes pour afficher les deux dernières heures d’activité suivie :

-- Top queries that waited on buffer
-- Note these are finished queries
WITH Aggregated AS (SELECT q.query_hash, SUM(total_query_wait_time_ms) total_wait_time_ms, SUM(total_query_wait_time_ms / avg_query_wait_time_ms) AS total_executions, MIN(qt.query_sql_text) AS sampled_query_text, MIN(wait_category_desc) AS wait_category_desc
                    FROM sys.query_store_query_text AS qt
                         INNER JOIN sys.query_store_query AS q ON qt.query_text_id=q.query_text_id
                         INNER JOIN sys.query_store_plan AS p ON q.query_id=p.query_id
                         INNER JOIN sys.query_store_wait_stats AS waits ON waits.plan_id=p.plan_id
                         INNER JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id=waits.runtime_stats_interval_id
                    WHERE wait_category_desc='Buffer IO' AND rsi.start_time>=DATEADD(HOUR, -2, GETUTCDATE())
                    GROUP BY q.query_hash), Ordered AS (SELECT query_hash, total_executions, total_wait_time_ms, sampled_query_text, wait_category_desc, ROW_NUMBER() OVER (ORDER BY total_wait_time_ms DESC, query_hash ASC) AS query_hash_row_number
                                                        FROM Aggregated)
SELECT OD.query_hash, OD.total_executions, OD.total_wait_time_ms, OD.sampled_query_text, OD.wait_category_desc, OD.query_hash_row_number
FROM Ordered AS OD
WHERE OD.query_hash_row_number <= 15 -- get top 15 rows by total_wait_time_ms
ORDER BY total_wait_time_ms DESC;
GO

Vous pouvez également utiliser la vue sys.query_store_runtime_stats, en vous concentrant sur les requêtes avec de grandes valeurs dans les colonnes avg_physical_io_reads et avg_num_physical_io_reads.

Afficher le nombre total d’E/S du journal pour les attentes WRITELOG

Si le type d’attente est WRITELOG, utilisez la requête suivante pour afficher le nombre total d’E/S du journal par instruction :

-- Top transaction log consumers
-- Adjust the time window by changing
-- rsi.start_time >= DATEADD(hour, -2, GETUTCDATE())
WITH AggregatedLogUsed
AS (SELECT q.query_hash,
           SUM(count_executions * avg_cpu_time / 1000.0) AS total_cpu_ms,
           SUM(count_executions * avg_cpu_time / 1000.0) / SUM(count_executions) AS avg_cpu_ms,
           SUM(count_executions * avg_log_bytes_used) AS total_log_bytes_used,
           MAX(rs.max_cpu_time / 1000.00) AS max_cpu_ms,
           MAX(max_logical_io_reads) max_logical_reads,
           COUNT(DISTINCT p.plan_id) AS number_of_distinct_plans,
           COUNT(DISTINCT p.query_id) AS number_of_distinct_query_ids,
           SUM(   CASE
                      WHEN rs.execution_type_desc = 'Aborted' THEN
                          count_executions
                      ELSE 0
                  END
              ) AS Aborted_Execution_Count,
           SUM(   CASE
                      WHEN rs.execution_type_desc = 'Regular' THEN
                          count_executions
                      ELSE 0
                  END
              ) AS Regular_Execution_Count,
           SUM(   CASE
                      WHEN rs.execution_type_desc = 'Exception' THEN
                          count_executions
                      ELSE 0
                  END
              ) AS Exception_Execution_Count,
           SUM(count_executions) AS total_executions,
           MIN(qt.query_sql_text) AS sampled_query_text
    FROM sys.query_store_query_text AS qt
        INNER JOIN sys.query_store_query AS q ON qt.query_text_id = q.query_text_id
        INNER JOIN sys.query_store_plan AS p ON q.query_id = p.query_id
        INNER JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id = p.plan_id
        INNER JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
    WHERE rs.execution_type_desc IN ( 'Regular', 'Aborted', 'Exception' )
          AND rsi.start_time >= DATEADD(HOUR, -2, GETUTCDATE())
    GROUP BY q.query_hash),
     OrderedLogUsed
AS (SELECT query_hash,
           total_log_bytes_used,
           number_of_distinct_plans,
           number_of_distinct_query_ids,
           total_executions,
           Aborted_Execution_Count,
           Regular_Execution_Count,
           Exception_Execution_Count,
           sampled_query_text,
           ROW_NUMBER() OVER (ORDER BY total_log_bytes_used DESC, query_hash ASC) AS query_hash_row_number
    FROM AggregatedLogUsed)
SELECT OD.total_log_bytes_used,
       OD.number_of_distinct_plans,
       OD.number_of_distinct_query_ids,
       OD.total_executions,
       OD.Aborted_Execution_Count,
       OD.Regular_Execution_Count,
       OD.Exception_Execution_Count,
       OD.sampled_query_text,
       OD.query_hash_row_number
FROM OrderedLogUsed AS OD
WHERE OD.query_hash_row_number <= 15 -- get top 15 rows by total_log_bytes_used
ORDER BY total_log_bytes_used DESC;
GO

Identifier les problèmes de performances tempdb

Les types d’attente courants associés à des problèmes liés à tempdb sont PAGELATCH_* (et non PAGEIOLATCH_*). Toutefois, les attentes PAGELATCH_* ne signifient pas toujours qu’il y a une contention tempdb. Cette attente peut aussi s’expliquer par une contention de page de données utilisateur-objet due à des requêtes concurrentes ciblant la même page de données. Pour confirmer la contention tempdb, utilisez sys.dm_exec_requests et vérifiez que la valeur wait_resource commence par 2:x:y, où 2 est tempdb, l’ID de base de données, x est l’ID de fichier et y est l’ID de page.

Pour une contention tempdb, il est courant de réduire ou réécrire le code d’application qui repose sur tempdb. Les zones courantes d’utilisation de tempdb sont les suivantes :

  • Tables temporaires
  • Variables de table
  • Paramètres table
  • Requêtes comportant des plans de requête qui utilisent des tris, des jointures hachées et des files d’attente

Pour plus d’informations, consultez tempdb dans Azure SQL.

Toutes les bases de données dans un pool élastique partagent la même base de données tempdb. Une utilisation élevée de l’espace tempdb par une base de données peut affecter d’autres bases de données dans le même pool élastique.

Principales requêtes qui utilisent des variables de table et des tables temporaires

Utilisez la requête suivante pour identifier les principales requêtes qui utilisent des variables de table et des tables temporaires :

SELECT plan_handle, execution_count, query_plan
INTO #tmpPlan
FROM sys.dm_exec_query_stats
     CROSS APPLY sys.dm_exec_query_plan(plan_handle);
GO

WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
SELECT plan_handle, stmt.stmt_details.value('@Database', 'varchar(max)') AS 'Database'
, stmt.stmt_details.value('@Schema', 'varchar(max)') AS 'Schema'
, stmt.stmt_details.value('@Table', 'varchar(max)') AS 'table'
INTO #tmp2
FROM
    (SELECT CAST(query_plan AS XML) sqlplan, plan_handle FROM #tmpPlan) AS p
        CROSS APPLY sqlplan.nodes('//sp:Object') AS stmt(stmt_details);
GO

SELECT t.plan_handle, [Database], [Schema], [table], execution_count
FROM
    (SELECT DISTINCT plan_handle, [Database], [Schema], [table]
     FROM #tmp2
     WHERE [table] LIKE '%@%' OR [table] LIKE '%#%') AS t
        INNER JOIN #tmpPlan AS t2 ON t.plan_handle=t2.plan_handle;
GO
DROP TABLE #tmpPlan
DROP TABLE #tmp2

Identifier les transactions longues

Utilisez la requête suivante pour identifier les transactions longues. Les transactions longues empêchent le nettoyage du magasin de versions persistantes (PVS). Pour plus d’informations, consultez l’article Résoudre les problèmes de récupération accélérée de base de données.

SELECT DB_NAME(dtr.database_id) 'database_name',
       sess.session_id,
       atr.name AS 'tran_name',
       atr.transaction_id,
       transaction_type,
       transaction_begin_time,
       database_transaction_begin_time, 
       transaction_state,
       is_user_transaction,
       sess.open_transaction_count,
       TRIM(REPLACE(
                REPLACE(
                            SUBSTRING(
                                        SUBSTRING(
                                                    txt.text,
                                                    (req.statement_start_offset / 2) + 1,
                                                    ((CASE req.statement_end_offset
                                                            WHEN -1 THEN
                                                                DATALENGTH(txt.text)
                                                            ELSE
                                                                req.statement_end_offset
                                                        END - req.statement_start_offset
                                                    ) / 2
                                                    ) + 1
                                                ),
                                        1,
                                        1000
                                    ),
                            CHAR(10),
                            ' '
                        ),
                CHAR(13),
                ' '
            )
            ) Running_stmt_text,
       recenttxt.text 'MostRecentSQLText'
FROM sys.dm_tran_active_transactions AS atr
    INNER JOIN sys.dm_tran_database_transactions AS dtr
        ON dtr.transaction_id = atr.transaction_id
    LEFT JOIN sys.dm_tran_session_transactions AS sess
        ON sess.transaction_id = atr.transaction_id
    LEFT JOIN sys.dm_exec_requests AS req
        ON req.session_id = sess.session_id
           AND req.transaction_id = sess.transaction_id
    LEFT JOIN sys.dm_exec_connections AS conn
        ON sess.session_id = conn.session_id
    OUTER APPLY sys.dm_exec_sql_text(req.sql_handle) AS txt
    OUTER APPLY sys.dm_exec_sql_text(conn.most_recent_sql_handle) AS recenttxt
WHERE atr.transaction_type != 2
      AND sess.session_id != @@spid
ORDER BY start_time ASC;

Identifier les problèmes de performances d’attente d’allocation de mémoire

Si votre principal type d’attente est RESOURCE_SEMAPHORE, vous avez peut-être un problème d’attente d’allocation de mémoire où les requêtes ne peuvent pas commencer à s’exécuter jusqu’à ce qu’elles obtiennent une allocation de mémoire suffisamment volumineuse.

Déterminer si une attente RESOURCE_SEMAPHORE est une attente principale

Utilisez la requête suivante pour déterminer si une attente RESOURCE_SEMAPHORE est une attente principale. Un autre indicateur serait une augmentation du rang du temps d’attente de RESOURCE_SEMAPHORE dans l’historique récent. Pour plus d’informations sur la résolution des problèmes d’attente liés aux allocations de mémoire, consultez Résoudre les problèmes de performances lentes ou de mémoire insuffisante causés par les allocations de mémoire dans SQL Server.

SELECT wait_type,
       SUM(wait_time) AS total_wait_time_ms
FROM sys.dm_exec_requests AS req
    INNER JOIN sys.dm_exec_sessions AS sess
        ON req.session_id = sess.session_id
WHERE is_user_process = 1
GROUP BY wait_type
ORDER BY SUM(wait_time) DESC;

Identifier les instructions de consommation élevée de la mémoire

Si vous rencontrez des erreurs de mémoire insuffisante dans Azure SQL Database, consultez sys.dm_os_out_of_memory_events. Pour plus d’informations, consultez Résoudre les erreurs de mémoire insuffisante avec Azure SQL Database.

Tout d’abord, modifiez le script ci-dessous pour mettre à jour les valeurs pertinentes de start_time et end_time. Ensuite, exécutez la requête suivante pour identifier les instructions de consommation élevée de la mémoire :

SELECT IDENTITY(INT, 1, 1) rowId,
    CAST(query_plan AS XML) query_plan,
    p.query_id
INTO #tmp
FROM sys.query_store_plan AS p
    INNER JOIN sys.query_store_runtime_stats AS r
        ON p.plan_id = r.plan_id
    INNER JOIN sys.query_store_runtime_stats_interval AS i
        ON r.runtime_stats_interval_id = i.runtime_stats_interval_id
WHERE start_time > '2018-10-11 14:00:00.0000000'
      AND end_time < '2018-10-17 20:00:00.0000000';

WITH cte
AS (SELECT query_id,
        query_plan,
        m.c.value('@SerialDesiredMemory', 'INT') AS SerialDesiredMemory
    FROM #tmp AS t
        CROSS APPLY t.query_plan.nodes('//*:MemoryGrantInfo[@SerialDesiredMemory[. > 0]]') AS m(c) )
SELECT TOP 50
    cte.query_id,
    t.query_sql_text,
    cte.query_plan,
    CAST(SerialDesiredMemory / 1024. AS DECIMAL(10, 2)) SerialDesiredMemory_MB
FROM cte
    INNER JOIN sys.query_store_query AS q
        ON cte.query_id = q.query_id
    INNER JOIN sys.query_store_query_text AS t
        ON q.query_text_id = t.query_text_id
ORDER BY SerialDesiredMemory DESC;

Identifier les 10 principales allocations de mémoire actives

Utilisez la requête suivante pour identifier les 10 principales allocations de mémoire actives :

SELECT TOP 10
    CONVERT(VARCHAR(30), GETDATE(), 121) AS runtime,
       r.session_id,
       r.blocking_session_id,
       r.cpu_time,
       r.total_elapsed_time,
       r.reads,
       r.writes,
       r.logical_reads,
       r.row_count,
       wait_time,
       wait_type,
       r.command,
       OBJECT_NAME(txt.objectid, txt.dbid) 'Object_Name',
       TRIM(REPLACE(REPLACE(SUBSTRING(SUBSTRING(TEXT, (r.statement_start_offset / 2) + 1, 
        (  (
            CASE r.statement_end_offset
                WHEN - 1
                    THEN DATALENGTH(TEXT)
                ELSE r.statement_end_offset
                END - r.statement_start_offset
            ) / 2
        ) + 1), 1, 1000), CHAR(10), ' '), CHAR(13), ' ')) AS stmt_text,
       mg.dop,                                               --Degree of parallelism
       mg.request_time,                                      --Date and time when this query requested the memory grant.
       mg.grant_time,                                        --NULL means memory has not been granted
       mg.requested_memory_kb / 1024.0 requested_memory_mb,  --Total requested amount of memory in megabytes
       mg.granted_memory_kb / 1024.0 AS granted_memory_mb,   --Total amount of memory actually granted in megabytes. NULL if not granted
       mg.required_memory_kb / 1024.0 AS required_memory_mb, --Minimum memory required to run this query in megabytes.
       max_used_memory_kb / 1024.0 AS max_used_memory_mb,
       mg.query_cost,                                        --Estimated query cost.
       mg.timeout_sec,                                       --Time-out in seconds before this query gives up the memory grant request.
       mg.resource_semaphore_id,                             --Non-unique ID of the resource semaphore on which this query is waiting.
       mg.wait_time_ms,                                      --Wait time in milliseconds. NULL if the memory is already granted.
       CASE mg.is_next_candidate --Is this process the next candidate for a memory grant
           WHEN 1 THEN
               'Yes'
           WHEN 0 THEN
               'No'
           ELSE
               'Memory has been granted'
       END AS 'Next Candidate for Memory Grant',
       qp.query_plan
FROM sys.dm_exec_requests AS r
    INNER JOIN sys.dm_exec_query_memory_grants AS mg
        ON r.session_id = mg.session_id
           AND r.request_id = mg.request_id
    CROSS APPLY sys.dm_exec_sql_text(mg.sql_handle) AS txt
    CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp
ORDER BY mg.granted_memory_kb DESC;

Suivi des connexions

La vue sys.dm_exec_connections permet de récupérer des informations sur les connexions établies avec une base de données spécifique, ainsi que les détails de chaque connexion. Si une base de données se trouve dans un pool élastique et que vous disposez d’autorisations suffisantes, la vue retourne l’ensemble de connexions pour toutes les autres bases de données dans le pool élastique. En outre, la vue sys.dm_exec_sessions permet de récupérer des informations sur toutes les connexions utilisateur et tâches internes actives.

Afficher les sessions actives

La requête suivante récupère des informations pour votre connexion et votre session actuelles. Pour afficher toutes les connexions et sessions, supprimez la clause WHERE.

Vous voyez toutes les sessions en cours d’exécution sur la base de données uniquement si vous disposez de l’autorisation VIEW DATABASE STATE sur la base de données lors de l’exécution des vues sys.dm_exec_requests et sys.dm_exec_sessions. Sinon, vous voyez uniquement la session active.

SELECT
    c.session_id, c.net_transport, c.encrypt_option,
    c.auth_scheme, s.host_name, s.program_name,
    s.client_interface_name, s.login_name, s.nt_domain,
    s.nt_user_name, s.original_login_name, c.connect_time,
    s.login_time
FROM sys.dm_exec_connections AS c
    INNER JOIN sys.dm_exec_sessions AS s
        ON c.session_id = s.session_id
WHERE c.session_id = @@SPID; --Remove to view all sessions, if permissions allow

Superviser les performances des requêtes

Des requêtes lentes ou longues peuvent consommer des ressources système significatives. Cette section montre comment détecter quelques problèmes courants liés aux performances de requête en utilisant la vue de gestion dynamique sys.dm_exec_query_stats. La vue contient une ligne par instruction de requête dans le plan en cache et la durée de vie des lignes est liée au plan lui-même. Lorsqu'un plan est supprimé du cache, les lignes correspondantes sont éliminées de cette vue. Si une requête n’a pas de plan mis en cache, par exemple parce que OPTION (RECOMPILE) est utilisé, elle n’est pas présente dans les résultats de cette vue.

Rechercher les principales requêtes par temps processeur

L’exemple suivant retourne des informations relatives aux 15 premières requêtes, classées sur la base du temps processeur moyen par exécution. Cet exemple agrège les requêtes selon leur hachage de requête et, logiquement, les requêtes équivalentes sont regroupées sur la base de leur consommation cumulée de ressources.

SELECT TOP 15 query_stats.query_hash AS Query_Hash,
    SUM(query_stats.total_worker_time) / SUM(query_stats.execution_count) AS Avg_CPU_Time,
     MIN(query_stats.statement_text) AS Statement_Text
FROM
    (SELECT QS.*,
        SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1,
            ((CASE statement_end_offset
                WHEN -1 THEN DATALENGTH(ST.text)
                ELSE QS.statement_end_offset END
            - QS.statement_start_offset)/2) + 1) AS statement_text
FROM sys.dm_exec_query_stats AS QS
    CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) AS ST
    ) AS query_stats
GROUP BY query_stats.query_hash
ORDER BY Avg_CPU_Time DESC;

Superviser les plans de requête pour le temps processeur cumulé

Un plan de requête inefficace peut également augmenter la consommation du processeur. L’exemple suivant détermine quelle requête utilise le plus de ressources de processeur cumulées dans l’historique récent.

SELECT
    highest_cpu_queries.plan_handle,
    highest_cpu_queries.total_worker_time,
    q.dbid,
    q.objectid,
    q.number,
    q.encrypted,
    q.[text]
FROM
    (SELECT TOP 15
            qs.plan_handle,
            qs.total_worker_time
        FROM
            sys.dm_exec_query_stats AS qs
        ORDER BY qs.total_worker_time desc
    ) AS highest_cpu_queries
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS q
ORDER BY highest_cpu_queries.total_worker_time DESC;

Superviser les requêtes bloquées

Des requêtes lentes ou longues peuvent contribuer à une consommation excessive de ressources et être la conséquence de requêtes bloquées. La cause du blocage peut être une conception médiocre d’application, de mauvais plans de requête, le manque d’index utiles, et ainsi de suite.

Vous pouvez utiliser la vue sys.dm_tran_locks pour obtenir des informations sur l’activité de verrouillage actuelle dans la base de données. Pour obtenir des exemples de code, consultez sys.dm_tran_locks. Pour plus d’informations la résolution des problèmes bloquants, consultez Comprendre et résoudre les problèmes bloquants Azure SQL.

Superviser les interblocages

Dans certains cas, plusieurs requêtes peuvent se bloquer entre elles, ce qui entraîne un blocage.

Vous pouvez créer une trace d’Événements étendus pour capturer les événements d’interblocage, puis rechercher les requêtes associées et leurs plans d’exécution dans le magasin des requêtes. Pour en savoir plus, consultez Analyser et empêcher les interblocages dans Azure SQL Database, notamment un labo pour provoquer un interblocage dans AdventureWorksLT. Découvrez-en plus sur les types de ressources susceptibles de se bloquer.