web-dev-qa-db-fra.com

Exécution de la requête contre DMVS pour les statistiques de requête et les plans d'exécution reliés à SyS.Databases

Je l'ai initialement posté comme une question différente dans Twitter #sqlhelp, mais je voulais le poster ici différemment.

J'essaie de créer un emploi qui suit des instructions les plus performantes pour diverses statistiques telles que des lectures, des écrivies, de la CPU, du nombre d'exécution, etc. à l'aide du DMVS dm_exec_query_stats, dm_exec_cached_plans, et dm_exec_sql_text, et initialement sys.databases.

Ce qui s'est passé, c'est que cette requête prendrait parfois environ 8 minutes pour exécuter sur un serveur chargé, mais dès que j'ai supprimé la jointure to sys.databases, n'a pris que 9 secondes. La majeure partie de cette durée était l'heure du processeur, avec très peu d'attentes, pas de blocage, pas de balayage, seulement 13406 Lectures logiques et 1562 LOB se lit.

Donc, ce que je veux savoir, c'est pourquoi la performance énorme frappe de rejoindre Sys.Databases ? Et pourquoi est-ce incompatible?

Ce qui s'est passé, c'est que lorsque j'ai dirigé la requête de test suivante, cela prendrait plus de 8 minutes parfois sans raison apparente, et d'autres fois terminés en moins de 11 secondes.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;
DECLARE @snapshot_timeoffset AS datetimeoffset(3) = CAST(SYSDATETIMEOFFSET() AS datetimeoffset(3));

SELECT
    @snapshot_timeoffset AS [snapshot_timeoffset]
    ,db.name AS [database_name]  
    ,OBJECT_SCHEMA_NAME(st.objectid, st.dbid) [schema_name] 
    ,OBJECT_NAME(st.objectid, st.dbid) [object_name]   
    ,cp.objtype
    ,cp.usecounts
    ,cp.refcounts
    -- find the offset of the actual statement being executed
    ,SUBSTRING(st.text, 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2 + 1
            END, 
            CASE 
                WHEN qs.statement_end_offset = 0 OR qs.statement_end_offset = -1 OR qs.statement_end_offset IS NULL THEN LEN(st.text)  
                ELSE qs.statement_end_offset/2 
            END - 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2
            END + 1 
        ) AS [statement]  
    ,qs.execution_count
    ,qs.total_logical_reads
    ,qs.last_logical_reads
    ,qs.min_logical_reads
    ,qs.max_logical_reads
    ,qs.total_logical_writes
    ,qs.last_logical_writes
    ,qs.min_logical_writes
    ,qs.max_logical_writes
    ,qs.total_physical_reads
    ,qs.last_physical_reads
    ,qs.min_physical_reads
    ,qs.max_physical_reads
    ,qs.total_worker_time
    ,qs.last_worker_time
    ,qs.min_worker_time
    ,qs.max_worker_time
    ,qs.total_clr_time
    ,qs.last_clr_time
    ,qs.min_clr_time
    ,qs.max_clr_time
    ,qs.total_elapsed_time
    ,qs.last_elapsed_time
    ,qs.min_elapsed_time
    ,qs.max_elapsed_time
    ,qs.total_rows
    ,qs.last_rows
    ,qs.min_rows
    ,qs.max_rows
    ,qs.last_execution_time
    ,qs.creation_time
    ,qs.sql_handle
    ,qs.plan_handle
    ,qs.statement_start_offset
    ,qs.statement_end_offset
INTO #QueryStats
FROM master.sys.dm_exec_query_stats qs   
INNER JOIN  master.sys.dm_exec_cached_plans cp 
ON qs.plan_handle = cp.plan_handle 
CROSS APPLY  master.sys.dm_exec_sql_text(qs.plan_handle) st 
INNER JOIN  master.sys.databases db
ON st.dbid = db.database_id;

Bad Plan

Par hasard, j'ai décidé de supprimer la join interne sur sys.databases et remplacez la recherche du nom de la base de données avec la fonction DB_NAME() O _ Fonction. secondes:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;
DECLARE @snapshot_timeoffset AS datetimeoffset(3) = CAST(SYSDATETIMEOFFSET() AS datetimeoffset(3));

SELECT
    @snapshot_timeoffset AS [snapshot_timeoffset]
    ,DB_NAME(st.dbid) AS [database_name]  
    ,OBJECT_SCHEMA_NAME(st.objectid, st.dbid) [schema_name] 
    ,OBJECT_NAME(st.objectid, st.dbid) [object_name]   
    ,cp.objtype
    ,cp.usecounts
    ,cp.refcounts
    -- find the offset of the actual statement being executed
    ,SUBSTRING(st.text, 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2 + 1
            END, 
            CASE 
                WHEN qs.statement_end_offset = 0 OR qs.statement_end_offset = -1 OR qs.statement_end_offset IS NULL THEN LEN(st.text)  
                ELSE qs.statement_end_offset/2 
            END - 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2
            END + 1 
        ) AS [statement]  
    ,qs.execution_count
    ,qs.total_logical_reads
    ,qs.last_logical_reads
    ,qs.min_logical_reads
    ,qs.max_logical_reads
    ,qs.total_logical_writes
    ,qs.last_logical_writes
    ,qs.min_logical_writes
    ,qs.max_logical_writes
    ,qs.total_physical_reads
    ,qs.last_physical_reads
    ,qs.min_physical_reads
    ,qs.max_physical_reads
    ,qs.total_worker_time
    ,qs.last_worker_time
    ,qs.min_worker_time
    ,qs.max_worker_time
    ,qs.total_clr_time
    ,qs.last_clr_time
    ,qs.min_clr_time
    ,qs.max_clr_time
    ,qs.total_elapsed_time
    ,qs.last_elapsed_time
    ,qs.min_elapsed_time
    ,qs.max_elapsed_time
    ,qs.total_rows
    ,qs.last_rows
    ,qs.min_rows
    ,qs.max_rows
    ,qs.last_execution_time
    ,qs.creation_time
    ,qs.sql_handle
    ,qs.plan_handle
    ,qs.statement_start_offset
    ,qs.statement_end_offset
INTO #QueryStats
FROM master.sys.dm_exec_query_stats qs   
INNER JOIN  master.sys.dm_exec_cached_plans cp 
ON qs.plan_handle = cp.plan_handle 
CROSS APPLY  master.sys.dm_exec_sql_text(qs.plan_handle) st;

enter image description here

Remarque: la fonction de valorisation de la table [SYSDMEXECCACHEDPLANS] Avait estimé CPU Coût 0.0522562, nombre estimé de lignes 52256 et nombre réel de lignes 52251 dans le bon plan. Toutefois, dans le plan précédent, la même fonction a maintenant nombre réel de lignes 327605402 , qui constitue un énorme divergence.

J'ai Ran Paul Randal's Séance d'événement étendue qui trace des types d'attente pour une session spécifique sur sa suggestion et obtenu la sortie suivante:

Wait Type            Count Total Wait (ms)  Resource Wait (ms)  Signal Wait (ms)
NETWORK_IO            4807            1119                1119                0
CXPACKET              9449             649                 620               29
SOS_SCHEDULER_YIELD 119763             370                   0              370
SLEEP_TASK           11212               2                   0                2
LATCH_EX               782               1                   1                0
LATCH_SH                 2               0                   0                0
LCK_M_S                  4               0                   0                0
EXECSYNC                 3               0                   0                0
PAGEIOLATCH_EX           4               0                   0                0
PAGELATCH_EX             1               0                   0                0
PAGELATCH_SH             7               0                   0                0
PAGELATCH_UP            55               0                   0                0

Notez qu'il y a très peu d'attente, mais le temps du processeur correspondait presque à la durée. Paul a mentionné que cela pourrait être la conflit de Spinlock, mais nous courons sur SQL 2014 SP1 CU2 et le bogue https://support.microsoft.com/en-ca/kb/302608 déjà fixé dans SP1. De plus, ne voyant rien sauter sur system_health.xel.

Une requête de sys.databases Par lui-même est toujours assez rapide.

6
Diane

L'accumulation Sos_scheduler_yield est comme si je suggérais sur #sqlhelp. Chacun de ceux-ci équivaut à 4 ms de temps de processeur pour la requête et affichent toujours du temps d'attente de ressources zéro, car il n'y a pas d'attente de ressource impliquée (le fil donne le processeur et passe directement au bas de la file d'attente annulée sur le planificateur).

Donc, cette requête était de baratter à travers la CPU sans avoir besoin d'autres ressources.

Ce n'est pas non plus rien à voir avec des spinlocks, car ils n'apparaissent pas comme un type d'attente spécifique (idée fausse commune que sos_scheduler_yield = Spinocks). J'ai suggéré que lorsque vous avez dit, il n'y avait aucune attente.

Quand il fonctionne lentement, cela retourne-t-il notamment plus de lignes que quand il court vite?

8
Paul S. Randal