Share via


Analyser les données de surveillance de l’observateur de base de données (aperçu)

S’applique à : Azure SQL DatabaseAzure SQL Managed Instance

Outre l’utilisation de tableaux de bord dans le Portail Azure, ou la création de visualisations pour afficher et analyser des données de surveillance SQL dans Power BI, Grafana, Azure Data Explorer ou Analyse en temps réel dans Microsoft Fabric, vous pouvez interroger directement votre magasin de données de surveillance.

Cet article contient des exemples de requêtes KQL et T-SQL qui vous aideront à démarrer l’analyse des données de surveillance collectées.

Utiliser KQL pour analyser les données de surveillance

Pour analyser les données de surveillance collectées, la méthode recommandée consiste à utiliser le Langage de requête Kusto (KQL). KQL est optimal pour interroger les données de télémétrie, les métriques et les journaux. Il offre un support étendu pour la recherche et l’analyse de texte, les opérateurs et les fonctions de séries chronologiques, l’analytique et l’agrégation, et bien d’autres constructions de langage qui facilitent l’analyse de données.

Sur le plan conceptuel, KQL est similaire à SQL. Il opère sur des entités de schéma telles que des tables et des colonnes, et prend en charge des opérations relationnelles telles que projeter, restreindre, joindre et résumer, correspondant aux clauses SELECT, JOIN, WHERE et GROUP BY en SQL.

Pour écrire et exécuter des requêtes KQL, vous pouvez utiliser Kusto Explorer ou l’interface utilisateur Web d’Azure Data Explorer. Kusto Explorer est un logiciel de bureau Windows complet, tandis que l’interface utilisateur Web d’Azure Data Explorer vous permet d’exécuter des requêtes KQL et de visualiser les résultats dans le navigateur sur n’importe quelle plateforme.

Vous pouvez également utiliser ces outils pour interroger une base de données d’Analyse en temps réel dans Microsoft Fabric. Pour vous connecter, ajoutez une nouvelle connexion à l’aide de l’URI de requête de votre base de données d’Analyse en temps réel. En outre, si vous utilisez l’Analyse en temps réel, vous pouvez analyser les données de surveillance à l’aide d’ensembles de requêtes KQL. Un ensemble de requêtes KQL peut être enregistré en tant qu’artefact Fabric partageable et utilisé pour créer des rapports Power BI.

Si vous ne connaissez pas KQL, les ressources suivantes peuvent vous aider à démarrer :

Les exemples suivants peuvent vous aider à écrire vos propres requêtes KQL pour afficher et analyser les données de surveillance SQL collectées. Vous pouvez également utiliser ces exemples comme point de départ dans la création de vos propres visualisations des données et tableaux de bord.

Utiliser KQL pour interroger la consommation des ressources au fil du temps

Dans cet exemple, la requête retourne des métriques de consommation de ressources (UC, Workers, débit d’écriture de journal, etc.) pour le réplica principal d’une base de données, un pool élastique ou une instance managée SQL au cours de la dernière heure. En plus de renvoyer le jeu de résultats, il le visualise sous la forme d’un graphique temporel.

Dans cet exemple et dans d’autres, modifiez les variables des instructions let pour qu’elles correspondent aux noms de votre serveur, base de données, groupe élastique ou instance SQL managed. Pour utiliser un intervalle de temps différent, modifiez la variable duration. Pour plus d’informations, consultez Littéraux timespan.

let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let duration = 1h;
sqldb_database_resource_utilization
| where sample_time_utc > ago(duration)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| project sample_time_utc,
          avg_cpu_percent,
          avg_instance_cpu_percent,
          avg_data_io_percent,
          avg_log_write_percent,
          max_worker_percent
| sort by sample_time_utc desc
| render timechart;

Utiliser KQL pour afficher les propriétés de base de données, de pool élastique ou d’instance managée SQL

Dans cet exemple, la requête retourne un jeu regroupant toutes les bases de données, pools élastiques ou instances managées SQL à partir desquels au moins un échantillon dans le jeu de donnéesPropriétés correspondant a été collecté au cours du dernier jour. En d’autres termes, chaque ligne représente une cible de surveillance avec ses propriétés les plus récentes observées.

La fonction arg_max() agrège les données pour retourner la ligne la plus récente du jeu de colonnes spécifié qui identifie une cible. Par exemple, pour les bases de données Azure SQL, ce jeu est logical_server_name, database_name, replica_type.

let duration = 1d;
sqldb_database_properties
| where sample_time_utc > ago(duration)
| summarize arg_max(sample_time_utc, *) by logical_server_name, database_name, replica_type
| project-rename last_sample_time_utc = sample_time_utc
| sort by tolower(logical_server_name) asc,
          tolower(database_name) asc,
          case(
              replica_type == "Primary", 0,
              replica_type == "Geo-replication forwarder", 1,
              replica_type == "Named secondary", 2,
              replica_type == "HA secondary", 3,
              4) asc;

Utiliser KQL pour interroger des statistiques d’exécution

Cette requête renvoie les requêtes consommant le plus de ressources dans votre patrimoine Azure SQL. Modifiez une variable pour classer les requêtes en fonction de n’importe quelle métrique du Magasin des requêtes, y compris le temps processeur, le temps écoulé, le nombre d’exécutions, etc. Vous pouvez également définir des variables pour filtrer par intervalle de temps, par type d’exécution de la requête et par texte de la requête. Définissez des variables pour vous concentrer sur un serveur logique, un pool élastique, une instance managée SQL ou une base de données spécifique.

La requête utilise le jeu de donnéesStatistiques d’exécution des requêtes pour renvoyer le nombre de requêtes les plus importantes que vous avez spécifié, et inclut leur classement selon toutes les autres métriques de consommation de ressources.

let topQueriesBy = "cpu_time"; // Set to one of the following metrics to return the top resource consuming queries:
// count_executions, duration, cpu_time, logical_io_reads, logical_io_writes, physical_io_reads, 
// num_physical_io_reads, clr_time, dop, query_max_used_memory, rowcount, log_bytes_used, tempdb_space_used 
let topQueries = 10; // Set the number of top queries to return
let endTime = now();
let startTime = endTime - 1d;
let logicalServerName = @""; // Optionally filter by logical server name
let elasticPoolName = @""; // Optionally filter by elastic pool name, if any databases are in elastic pools
let databaseName = @""; // Optionally filter by database name
let executionType = ""; // Optionally filter by execution type. Use Regular, Aborted, Exception.
let queryHash = ""; // Optionally filter by query hash (example: 0xBAAA461A6C93EA88)
let queryTextFragment = ""; // Optionally filter by a query text fragment
sqldb_database_query_runtime_stats
| where interval_start_time >= startTime and interval_end_time <= endTime
| where isempty(executionType) or execution_type_desc =~ executionType
| where isempty(logicalServerName) or logical_server_name =~ logicalServerName
| where isempty(elasticPoolName) or elastic_pool_name =~ elasticPoolName
| where isempty(databaseName) or database_name =~ databaseName
| summarize dcount_logical_servers = dcount(logical_server_name),
            any_logical_server_name = take_any(logical_server_name),
            dcount_elastic_pools = dcount(strcat(logical_server_name, "|", elastic_pool_name)),
            any_elastic_pool_name = take_any(elastic_pool_name),
            dcount_databases = dcount(strcat(logical_server_name, "|", database_name)),
            any_database_name = take_any(database_name),
            dcount_sql_module_name = dcount(sql_module_name),
            any_sql_module_name = take_any(sql_module_name),
            dcount_context_settings_id = dcount(context_settings_id),
            any_context_settings_id = take_any(context_settings_id),
            query_sql_text = take_any(query_sql_text),
            count_executions = sum(toreal(count_executions)),
            count_successful_executions = sumif(toreal(count_executions), execution_type_desc == "Regular"),
            count_aborted_executions = sumif(toreal(count_executions), execution_type_desc == "Aborted"),
            count_exception_executions = sumif(toreal(count_executions), execution_type_desc == "Exception"),
            duration_us = sum(avg_duration_us * count_executions),
            cpu_time_us = sum(avg_cpu_time_us * count_executions),
            logical_io_reads = sum(avg_logical_io_reads * count_executions),
            logical_io_writes = sum(avg_logical_io_writes * count_executions),
            physical_io_reads = sum(avg_physical_io_reads * count_executions),
            num_physical_io_reads = sum(avg_num_physical_io_reads * count_executions),
            clr_time_us = sum(avg_clr_time_us * count_executions),
            dop = sumif(avg_dop * count_executions, is_parallel_plan),
            query_max_used_memory = sum(avg_query_max_used_memory * count_executions),
            rowcount = sum(avg_rowcount * count_executions),
            log_bytes_used = sum(avg_log_bytes_used * count_executions),
            tempdb_space_used = sum(avg_tempdb_space_used * count_executions)
            by query_hash
| project logical_server_name = iif(dcount_logical_servers == 1, any_logical_server_name, strcat(any_logical_server_name, " (+", tostring(dcount_logical_servers - 1), ")")),
          elastic_pool_name = iif(dcount_elastic_pools == 1, any_elastic_pool_name, strcat(any_elastic_pool_name, " (+", tostring(dcount_elastic_pools - 1), ")")),
          database_name = iif(dcount_databases == 1, any_database_name, strcat(any_database_name, " (+", tostring(dcount_databases - 1), ")")),
          query_sql_text,
          count_executions,
          count_successful_executions,
          count_aborted_executions,
          count_exception_executions,
          duration_us,
          cpu_time_us,
          logical_io_reads,
          logical_io_writes,
          physical_io_reads,
          num_physical_io_reads,
          clr_time_us,
          dop,
          query_max_used_memory_kb = query_max_used_memory * 8,
          rowcount,
          log_bytes_used,
          tempdb_space_used_kb = tempdb_space_used * 8,
          sql_module_name = iif(dcount_sql_module_name == 1, any_sql_module_name, strcat(any_sql_module_name, " (+", tostring(dcount_sql_module_name - 1), ")")),
          context_settings_id = iif(dcount_context_settings_id == 1, tostring(any_context_settings_id), strcat(any_context_settings_id, " (+", tostring(dcount_context_settings_id - 1), ")")),
          query_hash
| sort by count_executions desc | extend count_executions_rank = row_rank_dense(count_executions)
| sort by duration_us desc | extend duration_rank = row_rank_dense(duration_us)
| sort by cpu_time_us desc | extend cpu_time_rank = row_rank_dense(cpu_time_us)
| sort by logical_io_reads desc | extend logical_io_reads_rank = row_rank_dense(logical_io_reads)
| sort by logical_io_writes desc | extend logical_io_writes_rank = row_rank_dense(logical_io_writes)
| sort by physical_io_reads desc | extend physical_io_reads_rank = row_rank_dense(physical_io_reads)
| sort by num_physical_io_reads desc | extend num_physical_io_reads_rank = row_rank_dense(num_physical_io_reads)
| sort by clr_time_us desc | extend clr_time_rank = row_rank_dense(clr_time_us)
| sort by dop desc | extend dop_rank = row_rank_dense(dop)
| sort by query_max_used_memory_kb desc | extend query_max_used_memory_rank = row_rank_dense(query_max_used_memory_kb)
| sort by rowcount desc | extend rowcount_rank = row_rank_dense(rowcount)
| sort by log_bytes_used desc | extend log_bytes_used_rank = row_rank_dense(log_bytes_used)
| sort by tempdb_space_used_kb desc | extend tempdb_space_used_rank = row_rank_dense(tempdb_space_used_kb)
| sort by case(
              topQueriesBy =~ "count_executions", toreal(count_executions),
              topQueriesBy =~ "duration", toreal(duration_us),
              topQueriesBy =~ "cpu_time", toreal(cpu_time_us),
              topQueriesBy =~ "logical_io_reads", toreal(logical_io_reads),
              topQueriesBy =~ "logical_io_writes", toreal(logical_io_writes),
              topQueriesBy =~ "physical_io_reads", toreal(physical_io_reads),
              topQueriesBy =~ "num_physical_io_reads", toreal(num_physical_io_reads),
              topQueriesBy =~ "clr_time", toreal(clr_time_us),
              topQueriesBy =~ "dop", toreal(dop),
              topQueriesBy =~ "query_max_used_memory", toreal(query_max_used_memory_kb),
              topQueriesBy =~ "rowcount", toreal(rowcount),
              topQueriesBy =~ "log_bytes_used", toreal(log_bytes_used),
              topQueriesBy =~ "tempdb_space_used", toreal(tempdb_space_used_kb),
              real(null)
              ) desc,
          count_executions desc
| project-away count_executions
| where isempty(queryHash) or query_hash == queryHash
| where isempty(queryTextFragment) or query_sql_text contains queryTextFragment
| take topQueries;

Utiliser KQL pour analyser les compteurs de performances au fil du temps

Dans cet exemple, la requête retourne des valeurs de compteur de performances pour un intervalle de temps qui démarre 30 minutes avant l’heure de fin spécifiée.

Cet exemple utilise des compteurs de performances cumulatifs tels que Total request count et Query optimizations/sec. Cumulatif signifie que la valeur du compteur continue d’augmenter à mesure que l’activité de requête SQL se produit. La requête de cet exemple calcule la différence, ou delta, entre la valeur du compteur dans chaque échantillon et sa valeur dans l’échantillon précédent pour obtenir le nombre de requêtes et d’optimisations qui se sont produites depuis l’échantillon précédent, puis visualise ces métriques sur un graphique temporel.

let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let endTime = datetime("2023-12-19 22:10:00");
let startTime = endTime - 30m;
sqldb_database_performance_counters_common
| where sample_time_utc between (startTime .. endTime)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| where cntr_type == 272696576 // restrict to cumulative counters
| where object_name =~ "Workload Group Stats" and counter_name in ("Total request count","Query optimizations/sec")
| project replica_id, sample_time_utc, object_name, counter_name, cntr_value
| sort by replica_id asc, counter_name asc, sample_time_utc asc
| extend delta_cntr_value = iif(cntr_value >= prev(cntr_value) and counter_name == prev(counter_name) and replica_id == prev(replica_id), cntr_value - prev(cntr_value), real(null)),
         delta_sample_time_utc = iif(sample_time_utc >= prev(sample_time_utc), datetime_diff("Millisecond", sample_time_utc, prev(sample_time_utc)), long(null))
| where isnotempty(delta_sample_time_utc)
| extend value = delta_cntr_value / delta_sample_time_utc * 1000
| summarize requests_per_sec = take_anyif(value, counter_name =~ "Total request count"),
            query_optimizations_per_sec = take_anyif(value, counter_name =~ "Query optimizations/sec")
            by sample_time_utc
| sort by sample_time_utc desc
| project sample_time_utc, requests_per_sec, query_optimizations_per_sec
| render timechart;

L’exemple suivant concerne les compteurs de performances à un instant donné qui signalent la valeur observée la plus récente, telle que Active memory grants count, Pending memory grants count et Processes blocked. L’intervalle de temps est les 30 dernières minutes.

let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let duration = 30m;
sqldb_database_performance_counters_common
| where sample_time_utc > ago(duration)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| where cntr_type == 65792 // restrict to point-in-time counters
| where (object_name =~ "General Statistics" and counter_name in ("Processes blocked"))
        or
        (object_name =~ "Resource Pool Stats" and counter_name in ("Active memory grants count","Pending memory grants count"))
| project sample_time_utc, counter_name, cntr_value
| render timechart;

L’exemple suivant utilise le jeu de données Compteurs de performances (détaillé) pour représenter graphiquement l’utilisation de l’UC pour les pools de ressources utilisateur et internes ainsi que les groupes de charge de travail dans Azure SQL Database. Pour plus d’informations, consultez Consommation de ressources par les charges de travail utilisateur et les processus internes.

Les charges de travail utilisateur s’exécutent dans les pools de ressources SloSharedPool1 ou UserPool, tandis que tous les autres pools de ressources sont utilisés pour différentes charges de travail système.

De même, les charges de travail utilisateur s’exécutent dans les groupes de charges de travail dont le nom commence par UserPrimaryGroup.DBId, tandis que tous les autres groupes de charges de travail sont utilisés pour différentes charges de travail système. Par exemple, les requêtes de surveillance de l’observateur de base de données s’exécutent dans le groupe de charge de travail SQLExternalMonitoringGroup.

let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let endTime = datetime("2023-12-19 22:10:00");
let startTime = endTime - 30m;
sqldb_database_performance_counters_detailed
| where sample_time_utc between (startTime .. endTime)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| where cntr_type == 537003264 // restrict to ratio percentage counters
| where object_name =~ "Resource Pool Stats" and counter_name in ("CPU usage %")
| project sample_time_utc, resource_pool = instance_name, cpu_percentage = cntr_value
| render timechart;

let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let endTime = datetime("2023-12-19 22:10:00");
let startTime = endTime - 30m;
sqldb_database_performance_counters_detailed
| where sample_time_utc between (startTime .. endTime)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| where cntr_type == 537003264 // restrict to ratio percentage counters
| where object_name =~ "Workload Group Stats" and counter_name in ("CPU usage %")
| project sample_time_utc, workload_group = instance_name, cpu_percentage = cntr_value
| render timechart;

Utiliser KQL pour analyser les attentes cumulatives au fil du temps

Cet exemple montre comment représenter graphiquement les principaux types d’attente SQL au cours d’un intervalle de temps. La requête calcule le temps d’attente cumulé pour chaque type d’attente, en millisecondes par seconde de temps écoulé. Vous pouvez ajuster les variables de requête pour définir l’heure de début et de fin de l’intervalle, le nombre de principaux types d’attente à inclure et le pas entre les points de données sur le graphique.

La requête utilise deux techniques pour améliorer les performances :

  • Utilisez l’opérateur KQL de partition avec la stratégie shuffle pour répartir le traitement des requêtes sur plusieurs nœuds de cluster, le cas échéant.
  • Utilisez la fonction materialize() pour faire persister un jeu de résultats intermédiaires qui est réutilisé pour calculer les temps d’attente les plus élevés et pour construire la série chronologique à représenter sous forme de graphique.
let logicalServer = @"your-server-name";
let databaseName = @"your-database-name";
let replicaType = "Primary";
let endTime = datetime("2023-12-19 22:10:00");
let startTime = endTime - 30m;
let top_wait_types = 10;
let chart_step = 30s;
let wait_type_sample = materialize (
sqldb_database_wait_stats
| where sample_time_utc between (startTime .. endTime)
| where logical_server_name =~ logicalServer
| where database_name =~ databaseName
| where replica_type =~ replicaType
| project replica_id, sample_time_utc, wait_type, wait_time_ms
| partition hint.strategy=shuffle by wait_type
(
sort by replica_id asc, sample_time_utc asc
| extend delta_wait_time_ms = iif(wait_time_ms >= prev(wait_time_ms) and replica_id == prev(replica_id), wait_time_ms - prev(wait_time_ms), long(null)),
         delta_sample_time_utc = iif(sample_time_utc >= prev(sample_time_utc), datetime_diff("Millisecond", sample_time_utc, prev(sample_time_utc)), long(null))
| where isnotempty(delta_sample_time_utc)
| extend wait_ms_per_s = toreal(delta_wait_time_ms) / delta_sample_time_utc * 1000
| project sample_time_utc, wait_type, wait_ms_per_s
)
);
let top_wait = (
wait_type_sample
| summarize total_wait_ms_per_s = sum(wait_ms_per_s) by wait_type
| top top_wait_types by total_wait_ms_per_s desc
| project-away total_wait_ms_per_s
);
wait_type_sample
| join kind=inner top_wait on wait_type
| project-away wait_type1
| make-series wait_ms_per_s = avgif(wait_ms_per_s, isfinite(wait_ms_per_s)) default = long(null) on sample_time_utc from startTime to endTime step chart_step by wait_type
| project wait_type, sample_time_utc, wait_ms_per_s
| render timechart;

Utiliser T-SQL pour analyser les données de surveillance

Si vous connaissez déjà T-SQL, vous pouvez commencer à interroger et à analyser immédiatement les données de surveillance SQL sans avoir à apprendre KQL. Toutefois, KQL est le langage recommandé pour l’interrogation de données dans Azure Data Explorer ou l’Analyse en temps réel, car il offre une prise en charge inégalée pour l’interrogation des données de télémétrie.

Vous pouvez vous connecter à votre base de données Azure Data Explorer ou d’Analyse en temps réel à partir de SQL Server Management Studio (SSMS), d’Azure Data Studio et d’autres outils courants. Vous pouvez interroger une base de données Azure Data Explorer ou KQL comme s’il s’agissait d’un serveur SQL Server ou d’une base de données Azure SQL. Pour plus d’informations, consultez Interroger des données dans Azure Data Explorer à l’aide de l’émulation SQL Server.

Remarque

Toutes les constructions T-SQL ne sont pas prises en charge dans Azure Data Explorer et l’Analyse en temps réel. Pour plus d’informations, consultez Interroger des données à l’aide de T-SQL.

L’aide-mémoire SQL pour langage de requête Kusto peut vous aider à traduire vos requêtes T-SQL en KQL si vous constatez que la prise en charge T-SQL est insuffisante pour vos besoins, ou si vous souhaitez convertir vos requêtes T-SQL en KQL pour utiliser ses fonctionnalités d’analyses avancées.

Les exemples suivants vous montrent comment interroger des données de surveillance dans le magasin de données de l’observateur de base de données à l’aide de T-SQL.

Utiliser T-SQL pour analyser la consommation des ressources au fil du temps

Dans cet exemple, la requête retourne des métriques de consommation de ressources (UC, Workers, débit d’écriture de journal, etc.) pour le réplica principal d’une base de données, un pool élastique ou une instance managée SQL au cours de la dernière heure.

Dans cet exemple et dans d’autres, modifiez les variables de l’instruction DECLARE pour qu’elles correspondent aux noms de votre serveur, base de données, pool élastique ou instance managée SQL.

DECLARE @LogicalServerName sysname = 'your-server-name',
        @DatabaseName sysname = 'your-database-name',
        @ReplicaType sysname = 'Primary',
        @DurationMinutes int = 60;

SELECT sample_time_utc,
       avg_cpu_percent,
       avg_instance_cpu_percent,
       avg_data_io_percent,
       avg_log_write_percent,
       max_worker_percent
FROM sqldb_database_resource_utilization
WHERE sample_time_utc > DATEADD(minute, -@DurationMinutes, SYSUTCDATETIME())
      AND
      logical_server_name = @LogicalServerName
      AND
      database_name = @DatabaseName
      AND
      replica_type = @ReplicaType
ORDER BY sample_time_utc DESC;

Utiliser T-SQL pour afficher les propriétés de base de données, de pool élastique ou d’instance managée SQL

Dans cet exemple, la requête retourne un jeu regroupant toutes les bases de données, pools élastiques ou instances managées SQL à partir desquels au moins un échantillon du jeu de donnéesPropriétés correspondant a été collecté au cours des 24 dernières heures. En d’autres termes, chaque ligne représente une cible de surveillance avec ses propriétés les plus récentes observées.

DECLARE @DurationHours int = 24;

SELECT p.sample_time_utc,
       p.logical_server_name,
       p.database_name,
       p.replica_type,
       p.database_id,
       p.elastic_pool_name,
       p.service_tier,
       p.service_level_objective,
       p.logical_cpu_count,
       p.database_engine_memory_mb,
       p.compatibility_level,
       p.updateability,
       p.database_engine_build_time,
       p.database_engine_start_time_utc
FROM sqldb_database_properties AS p
INNER JOIN (
           SELECT logical_server_name,
                  database_name,
                  replica_type,
                  MAX(sample_time_utc) AS last_sample_time_utc
           FROM sqldb_database_properties
           WHERE sample_time_utc > DATEADD(hour, -@DurationHours, SYSUTCDATETIME())
           GROUP BY logical_server_name,
                    database_name,
                    replica_type
           ) AS ls
ON p.logical_server_name = ls.logical_server_name
   AND
   p.database_name = ls.database_name
   AND
   p.replica_type = ls.replica_type
   AND
   p.sample_time_utc = ls.last_sample_time_utc
WHERE p.sample_time_utc > DATEADD(hour, -@DurationHours, SYSUTCDATETIME())
ORDER BY LOWER(logical_server_name) ASC,
         LOWER(database_name) ASC,
         CASE replica_type
              WHEN 'Primary' THEN 0
              WHEN 'Geo-replication forwarder' THEN 1
              WHEN 'Named secondary' THEN 2
              WHEN 'HA secondary' THEN 3
         END ASC;

Utiliser T-SQL pour interroger des statistiques d’exécution

Cette requête renvoie les requêtes consommant le plus de ressources dans votre patrimoine Azure SQL. Modifiez la variable @TopQueriesBy pour trouver les requêtes les plus performantes en fonction de n’importe quelle métrique du Magasin de requêtes, y compris le temps processeur, le temps écoulé, le nombre d’exécutions, etc. Vous pouvez également définir des variables pour filtrer par intervalle de temps, type d’exécution de la requête et hachage d’une requête spécifique, ou pour vous concentrer sur les bases de données d’un serveur logique, d’un pool élastique ou d’une instance managée SQL spécifique.

La requête utilise le jeu de donnéesStatistiques d’exécution des requêtes pour retourner les requêtes les plus performantes que vous spécifiez. Elle renvoie également leur classement pour toutes les autres mesures de consommation de ressources.

DECLARE @EndTime datetime2 = SYSUTCDATETIME(),
        @StartTime datetime2 = DATEADD(hour, -24, SYSUTCDATETIME()),
        /* 
        Set the next variable to one of the following metrics to return the top resource consuming queries:
        executions, cpu_time, duration, logical_io_reads, physical_io_reads, num_physical_io_reads, 
        clr_time, query_max_used_memory, log_bytes_used, tempdb_space_used, row_count, dop
        */
        @TopQueriesBy varchar(30) = 'cpu_time',
        @TopQueries int = 10,
        @LogicalServerName sysname = '', -- Optionally filter by logical server name
        @ElasticPoolName sysname = '', -- Optionally filter by elastic pool name, if any databases are in elastic pools
        @DatabaseName sysname = '', -- Optionally filter by database name
        @ExecutionType varchar(30) = '', -- Optionally filter by execution type. Use Regular, Aborted, Exception.
        @QueryHash varchar(18) = ''; -- Optionally filter by query hash (example: 0xBAAA461A6C93EA88)

SELECT TOP (@TopQueries) 
       CONCAT(logical_server_name, IIF(count_logical_servers > 1, CONCAT(' (+', CAST(count_logical_servers - 1 AS varchar(11)), ')'), '')) AS logical_server_name,
       CONCAT(database_name, IIF(count_databases > 1, CONCAT(' (+', CAST(count_databases - 1 AS varchar(11)), ')'), '')) AS database_name,
       query_sql_text,
       CONCAT(CAST(query_id AS varchar(11)), IIF(count_queries > 1, CONCAT(' (+', CAST(count_queries - 1 AS varchar(11)), ')'), '')) AS query_id,
       CONCAT(CAST(plan_id AS varchar(11)), IIF(count_plans > 1, CONCAT(' (+', CAST(count_plans - 1 AS varchar(11)), ')'), '')) AS plan_id,
       regular_executions,
       aborted_executions,
       exception_executions,
       cpu_time_us,
       duration_us,
       logical_io_reads,
       physical_io_reads,
       num_physical_io_reads,
       clr_time_us,
       query_max_used_memory_kb,
       log_bytes_used,
       tempdb_space_used_kb,
       row_count,
       dop,
       query_hash,
       executions_rank,
       cpu_time_rank,
       duration_rank,
       logical_io_reads_rank,
       physical_io_reads_rank,
       num_physical_io_reads_rank,
       clr_time_rank,
       query_max_used_memory_rank,
       log_bytes_used_rank,
       tempdb_space_used_rank,
       row_count_rank,
       dop_rank
FROM (
     SELECT *,
            DENSE_RANK() OVER (ORDER BY executions DESC) AS executions_rank,
            DENSE_RANK() OVER (ORDER BY cpu_time_us DESC) AS cpu_time_rank,
            DENSE_RANK() OVER (ORDER BY duration_us DESC) AS duration_rank,
            DENSE_RANK() OVER (ORDER BY logical_io_reads DESC) AS logical_io_reads_rank,
            DENSE_RANK() OVER (ORDER BY physical_io_reads DESC) AS physical_io_reads_rank,
            DENSE_RANK() OVER (ORDER BY num_physical_io_reads DESC) AS num_physical_io_reads_rank,
            DENSE_RANK() OVER (ORDER BY clr_time_us DESC) AS clr_time_rank,
            DENSE_RANK() OVER (ORDER BY query_max_used_memory_kb DESC) AS query_max_used_memory_rank,
            DENSE_RANK() OVER (ORDER BY log_bytes_used DESC) AS log_bytes_used_rank,
            DENSE_RANK() OVER (ORDER BY tempdb_space_used_kb DESC) AS tempdb_space_used_rank,
            DENSE_RANK() OVER (ORDER BY row_count DESC) AS row_count_rank,
            DENSE_RANK() OVER (ORDER BY dop DESC) AS dop_rank
     FROM (
          SELECT query_hash,
                 COUNT(DISTINCT(logical_server_name)) AS count_logical_servers,
                 MAX(logical_server_name) AS logical_server_name,
                 COUNT(DISTINCT(database_name)) AS count_databases,
                 MAX(database_name) AS database_name,
                 COUNT(DISTINCT(query_id)) AS count_queries,
                 MAX(query_id) AS query_id,
                 COUNT(DISTINCT(plan_id)) AS count_plans,
                 MAX(plan_id) AS plan_id,
                 MAX(query_sql_text) AS query_sql_text,
                 SUM(IIF(execution_type_desc = 'Regular', count_executions, 0)) AS regular_executions,
                 SUM(IIF(execution_type_desc = 'Aborted', count_executions, 0)) AS aborted_executions,
                 SUM(IIF(execution_type_desc = 'Exception', count_executions, 0)) AS exception_executions,
                 SUM(count_executions) AS executions,
                 SUM(avg_cpu_time_us * count_executions) AS cpu_time_us,
                 SUM(avg_duration_us * count_executions) AS duration_us,
                 SUM(avg_logical_io_reads * count_executions) AS logical_io_reads,
                 SUM(avg_physical_io_reads * count_executions) AS physical_io_reads,
                 SUM(avg_num_physical_io_reads * count_executions) AS num_physical_io_reads,
                 SUM(avg_clr_time_us * count_executions) AS clr_time_us,
                 SUM(avg_query_max_used_memory * count_executions) * 8 AS query_max_used_memory_kb,
                 SUM(avg_log_bytes_used * count_executions) AS log_bytes_used,
                 SUM(avg_tempdb_space_used * count_executions) * 8 AS tempdb_space_used_kb,
                 SUM(avg_rowcount * count_executions) AS row_count,
                 SUM(IIF(is_parallel_plan = 1, avg_dop * count_executions, NULL)) AS dop
          FROM sqldb_database_query_runtime_stats
          WHERE interval_start_time >= @StartTime AND interval_end_time <= @EndTime
                AND
                (@ExecutionType = '' OR LOWER(execution_type_desc) = LOWER(@ExecutionType))
                AND
                (@LogicalServerName = '' OR LOWER(logical_server_name) = LOWER(@LogicalServerName))
                AND
                (@ElasticPoolName = '' OR LOWER(elastic_pool_name) = LOWER(@ElasticPoolName))
                AND
                (@DatabaseName = '' OR LOWER(database_name) = LOWER(@DatabaseName))
          GROUP BY query_hash
          ) AS rsa
     ) AS rsar
WHERE @QueryHash = '' OR LOWER(query_hash) = LOWER(@QueryHash)
ORDER BY CASE @TopQueriesBy
              WHEN 'executions' THEN executions_rank
              WHEN 'cpu_time' THEN cpu_time_rank
              WHEN 'duration' THEN duration_rank
              WHEN 'logical_io_reads' THEN logical_io_reads_rank
              WHEN 'physical_io_reads' THEN physical_io_reads_rank
              WHEN 'num_physical_io_reads' THEN num_physical_io_reads_rank
              WHEN 'clr_time' THEN clr_time_rank
              WHEN 'query_max_used_memory' THEN query_max_used_memory_rank
              WHEN 'log_bytes_used' THEN log_bytes_used_rank
              WHEN 'tempdb_space_used' THEN tempdb_space_used_rank
              WHEN 'row_count' THEN row_count_rank
              WHEN 'dop' THEN dop_rank
         END ASC;

Utiliser T-SQL pour analyser les compteurs de performances au fil du temps

Dans cet exemple, la requête renvoie des valeurs de compteur de performances pour les 30 dernières minutes.

Cet exemple utilise des compteurs de performances cumulatifs tels que Total request count et Query optimizations/sec. Cumulatif signifie que la valeur du compteur continue d’augmenter à mesure que l’activité de requête se produit. La requête utilise la fonction analytique LAG() pour calculer la différence, ou delta, entre la valeur du compteur dans chaque échantillon et sa valeur dans l’échantillon précédent pour obtenir le nombre de requêtes et d’optimisations qui se sont produites depuis l’échantillon précédent.

DECLARE @LogicalServerName sysname = 'your-server-name',
        @DatabaseName sysname = 'your-database-name',
        @ReplicaType sysname = 'Primary',
        @DurationMinutes int = 30;

SELECT sample_time_utc,
       SUM(IIF(
              counter_name = 'Total request count',
              CAST((cntr_value - prev_cntr_value) AS decimal) / DATEDIFF(millisecond, prev_sample_time_utc, sample_time_utc) * 1000,
              NULL
              )) AS requests_per_second,
       SUM(IIF(
              counter_name = 'Query optimizations/sec',
              CAST((cntr_value - prev_cntr_value) AS decimal) / DATEDIFF(millisecond, prev_sample_time_utc, sample_time_utc) * 1000,
              NULL
              )) AS query_optimizations_per_second
FROM (
     SELECT sample_time_utc,
            LAG(sample_time_utc) OVER (PARTITION BY replica_id, object_name, counter_name ORDER BY sample_time_utc ASC) AS prev_sample_time_utc,
            counter_name,
            cntr_value,
            LAG(cntr_value) OVER (PARTITION BY replica_id, object_name, counter_name ORDER BY sample_time_utc ASC) AS prev_cntr_value
     FROM sqldb_database_performance_counters_common
     WHERE sample_time_utc > DATEADD(minute, -@DurationMinutes, SYSUTCDATETIME())
           AND
           logical_server_name = @LogicalServerName
           AND
           database_name = @DatabaseName
           AND
           replica_type = @ReplicaType
           AND
           cntr_type = 272696576 /* restrict to cumulative counters */
           AND
           object_name = 'Workload Group Stats'
           AND
           counter_name IN ('Total request count','Query optimizations/sec')
     ) AS pc
WHERE cntr_value >= prev_cntr_value
      AND
      sample_time_utc >= prev_sample_time_utc
GROUP BY sample_time_utc
ORDER BY sample_time_utc DESC;

Utiliser T-SQL pour analyser les compteurs de performances à un instant donné

L’exemple suivant concerne les compteurs de performances à un instant donné qui signalent la valeur observée la plus récente, telle que Active memory grants count, Pending memory grants count et Processes blocked.

DECLARE @LogicalServerName sysname = 'your-server-name',
        @DatabaseName sysname = 'your-database-name',
        @ReplicaType sysname = 'Primary',
        @DurationMinutes int = 30;

SELECT sample_time_utc,
       SUM(IIF(
              counter_name = 'Processes blocked',
              cntr_value,
              NULL
              )) AS processes_blocked,
       SUM(IIF(
              counter_name = 'Active memory grants count',
              cntr_value,
              NULL
              )) AS active_memory_grants,
       SUM(IIF(
              counter_name = 'Pending memory grants count',
              cntr_value,
              NULL
              )) AS pending_memory_grants
FROM (
     SELECT sample_time_utc,
            counter_name,
            cntr_value
     FROM sqldb_database_performance_counters_common
     WHERE sample_time_utc > DATEADD(minute, -@DurationMinutes, SYSUTCDATETIME())
         AND
         logical_server_name = @LogicalServerName
         AND
         database_name = @DatabaseName
         AND
         replica_type = @ReplicaType
         AND
         cntr_type = 65792 /* restrict to point-in-time counters */
         AND
         (
         (object_name = 'General Statistics' AND counter_name IN ('Processes blocked'))
         OR
         (object_name = 'Resource Pool Stats' AND counter_name IN ('Active memory grants count','Pending memory grants count'))
         )
     ) AS pc
GROUP BY sample_time_utc
ORDER BY sample_time_utc DESC;

Utiliser T-SQL pour analyser les attentes cumulatives au fil du temps

Dans cet exemple, la requête renvoie les 10 principaux types d’attente par le temps d’attente cumulé moyen sur un intervalle de 30 minutes. Cumulatif signifie que la requête calcule la durée totale, en millisecondes, passée en attente sous chaque type d’attente par toutes les requêtes toutes les secondes. Étant donné que plusieurs requêtes peuvent s’exécuter (et attendre) simultanément, le temps d’attente cumulé de chaque seconde peut être supérieur à une seconde.

DECLARE @LogicalServerName sysname = 'your-server-name',
        @DatabaseName sysname = 'your-database-name',
        @ReplicaType sysname = 'Primary',
        @DurationMinutes int = 30;

SELECT TOP (10) wait_type,
                SUM(CAST((wait_time_ms - prev_wait_time_ms) AS decimal)) * 1000
                /
                SUM(DATEDIFF(millisecond, prev_sample_time_utc, sample_time_utc))
                AS wait_time_ms_per_sec
FROM (
     SELECT sample_time_utc,
            LAG(sample_time_utc) OVER (PARTITION BY replica_id, wait_type ORDER BY sample_time_utc ASC) AS prev_sample_time_utc,
            wait_type,
            wait_time_ms,
            LAG(wait_time_ms) OVER (PARTITION BY replica_id, wait_type ORDER BY sample_time_utc ASC) AS prev_wait_time_ms
     FROM sqldb_database_wait_stats
     WHERE sample_time_utc > DATEADD(minute, -@DurationMinutes, SYSUTCDATETIME())
         AND
         logical_server_name = @LogicalServerName
         AND
         database_name = @DatabaseName
         AND
         replica_type = @ReplicaType
     ) AS w
WHERE sample_time_utc >= prev_sample_time_utc
      AND
      wait_time_ms >= prev_wait_time_ms
GROUP BY wait_type
ORDER BY wait_time_ms_per_sec DESC;