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;
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;
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.
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?