Je voudrais savoir comment identifier la requête exacte ou le proc stocké qui remplit réellement le journal transactionnel de la base de données TEMPDB.
De http://www.sqlservercentral.com/scripts/tempdb/72007/
;WITH task_space_usage AS (
-- SUM alloc/delloc pages
SELECT session_id,
request_id,
SUM(internal_objects_alloc_page_count) AS alloc_pages,
SUM(internal_objects_dealloc_page_count) AS dealloc_pages
FROM sys.dm_db_task_space_usage WITH (NOLOCK)
WHERE session_id <> @@SPID
GROUP BY session_id, request_id
)
SELECT TSU.session_id,
TSU.alloc_pages * 1.0 / 128 AS [internal object MB space],
TSU.dealloc_pages * 1.0 / 128 AS [internal object dealloc MB space],
EST.text,
-- Extract statement from sql text
ISNULL(
NULLIF(
SUBSTRING(
EST.text,
ERQ.statement_start_offset / 2,
CASE WHEN ERQ.statement_end_offset < ERQ.statement_start_offset
THEN 0
ELSE( ERQ.statement_end_offset - ERQ.statement_start_offset ) / 2 END
), ''
), EST.text
) AS [statement text],
EQP.query_plan
FROM task_space_usage AS TSU
INNER JOIN sys.dm_exec_requests ERQ WITH (NOLOCK)
ON TSU.session_id = ERQ.session_id
AND TSU.request_id = ERQ.request_id
OUTER APPLY sys.dm_exec_sql_text(ERQ.sql_handle) AS EST
OUTER APPLY sys.dm_exec_query_plan(ERQ.plan_handle) AS EQP
WHERE EST.text IS NOT NULL OR EQP.query_plan IS NOT NULL
ORDER BY 3 DESC;
[~ # ~] modifier [~ # ~]
Comme Martin l'a souligné dans un commentaire, cela ne trouverait pas les transactions actives qui occupent de l'espace dans tempdb, il ne trouvera que les transactions actives requêtes qui utilisent actuellement de l'espace là-bas (et probablement coupables de l'utilisation actuelle du journal). Il peut donc y avoir une transaction ouverte, mais la requête réelle à l'origine du problème n'est plus en cours d'exécution.
Vous pouvez modifier le inner join
sur sys.dm_exec_requests
à un left outer join
, vous renverrez des lignes pour les sessions qui n'exécutent pas actuellement de requêtes.
La requête que Martin a postée ...
SELECT database_transaction_log_bytes_reserved,session_id
FROM sys.dm_tran_database_transactions AS tdt
INNER JOIN sys.dm_tran_session_transactions AS tst
ON tdt.transaction_id = tst.transaction_id
WHERE database_id = 2;
... identifierait session_id
s avec des transactions actives occupant de l'espace de journal, mais vous ne pourrez pas nécessairement déterminer la requête réelle à l'origine du problème, car si elle n'est pas en cours d'exécution, elle ne sera pas capturée dans la requête ci-dessus pour les requêtes actives. Vous pourrez peut-être vérifier de manière réactive la requête la plus récente à l'aide de DBCC INPUTBUFFER
mais cela peut ne pas vous dire ce que vous voulez entendre. Vous pouvez joindre de manière externe pour capturer ceux qui s'exécutent activement, par exemple:
SELECT tdt.database_transaction_log_bytes_reserved,tst.session_id,
t.[text], [statement] = COALESCE(NULLIF(
SUBSTRING(
t.[text],
r.statement_start_offset / 2,
CASE WHEN r.statement_end_offset < r.statement_start_offset
THEN 0
ELSE( r.statement_end_offset - r.statement_start_offset ) / 2 END
), ''
), t.[text])
FROM sys.dm_tran_database_transactions AS tdt
INNER JOIN sys.dm_tran_session_transactions AS tst
ON tdt.transaction_id = tst.transaction_id
LEFT OUTER JOIN sys.dm_exec_requests AS r
ON tst.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
WHERE tdt.database_id = 2;
Vous pouvez également utiliser le DMV sys.dm_db_session_space_usage
pour voir l'utilisation globale de l'espace par session (mais encore une fois, vous risquez de ne pas obtenir de résultats valides pour la requête; si la requête n'est pas active, ce que vous récupérez n'est peut-être pas le véritable coupable).
;WITH s AS
(
SELECT
s.session_id,
[pages] = SUM(s.user_objects_alloc_page_count
+ s.internal_objects_alloc_page_count)
FROM sys.dm_db_session_space_usage AS s
GROUP BY s.session_id
HAVING SUM(s.user_objects_alloc_page_count
+ s.internal_objects_alloc_page_count) > 0
)
SELECT s.session_id, s.[pages], t.[text],
[statement] = COALESCE(NULLIF(
SUBSTRING(
t.[text],
r.statement_start_offset / 2,
CASE WHEN r.statement_end_offset < r.statement_start_offset
THEN 0
ELSE( r.statement_end_offset - r.statement_start_offset ) / 2 END
), ''
), t.[text])
FROM s
LEFT OUTER JOIN
sys.dm_exec_requests AS r
ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
ORDER BY s.[pages] DESC;
Avec toutes ces requêtes à votre disposition, vous devriez être en mesure de préciser qui utilise tempdb et comment, surtout si vous les interceptez dans l'acte.
SORT_IN_TEMPDB
option si elle n'est pas nécessaireVous pouvez également considérer que votre utilisation du journal tempdb peut être causée par des processus internes sur lesquels vous avez peu ou pas de contrôle - par exemple, la messagerie de base de données, les notifications d'événements, les notifications de requête et le courtier de services utilisent tous tempdb d'une manière ou d'une autre. Vous pouvez arrêter d'utiliser ces fonctionnalités, mais si vous les utilisez, vous ne pouvez pas dicter comment et quand ils utilisent tempdb.
SELECT tst.[session_id],
s.[login_name] AS [Login Name],
DB_NAME (tdt.database_id) AS [Database],
tdt.[database_transaction_begin_time] AS [Begin Time],
tdt.[database_transaction_log_record_count] AS [Log Records],
tdt.[database_transaction_log_bytes_used] AS [Log Bytes Used],
tdt.[database_transaction_log_bytes_reserved] AS [Log Bytes Rsvd],
SUBSTRING(st.text, (r.statement_start_offset/2)+1,
((CASE r.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE r.statement_end_offset
END - r.statement_start_offset)/2) + 1) AS statement_text,
st.[text] AS [Last T-SQL Text],
qp.[query_plan] AS [Last Plan]
FROM sys.dm_tran_database_transactions tdt
JOIN sys.dm_tran_session_transactions tst
ON tst.[transaction_id] = tdt.[transaction_id]
JOIN sys.[dm_exec_sessions] s
ON s.[session_id] = tst.[session_id]
JOIN sys.dm_exec_connections c
ON c.[session_id] = tst.[session_id]
LEFT OUTER JOIN sys.dm_exec_requests r
ON r.[session_id] = tst.[session_id]
CROSS APPLY sys.dm_exec_sql_text (c.[most_recent_sql_handle]) AS st
OUTER APPLY sys.dm_exec_query_plan (r.[plan_handle]) AS qp
WHERE DB_NAME (tdt.database_id) = 'tempdb'
ORDER BY [Log Bytes Used] DESC
GO
Merci pour ce post, probablement le seul du genre. Mon test était simple, créez une table temporaire et assurez-vous qu'elle s'affiche lorsque j'exécute l'une des requêtes de ce message ... une ou deux seulement ont vraiment réussi. Je l'ai corrigé pour rejoindre le T-SQL, je l'ai optimisé pour des exécutions plus longues et je l'ai rendu assez utile. Faites-moi savoir si j'ai raté quelque chose, mais jusqu'à présent, vous avez obtenu un script automatisé/en boucle. Il fournit un moyen d'évaluer quelle requête/SPID est le délinquant sur une période de temps en utilisant la requête d'écart type (STDEV) ci-dessous.
Cela fonctionne toutes les 3 minutes pendant 40 fois, donc 2 heures. Modifiez les paramètres comme bon vous semble.
Il y a un filtre WHERE> 50 pages ci-dessous que les gens voudront peut-être effacer au cas où vous auriez beaucoup de petits tableaux. Sinon, vous n'attraperez pas cette nuance avec le ci-dessous tel qu'il est ...
Prendre plaisir!
DECLARE @minutes_apart INT; SET @minutes_apart = 3
DECLARE @how_many_times INT; SET @how_many_times = 40
--DROP TABLE tempdb..TempDBUsage
--SELECT * FROM tempdb..TempDBUsage
--SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC
DECLARE @delay_string NVARCHAR(8); SET @delay_string = '00:' + RIGHT('0'+ISNULL(CAST(@minutes_apart AS NVARCHAR(2)), ''),2) + ':00'
DECLARE @counter INT; SET @counter = 1
SET NOCOUNT ON
if object_id('tempdb..TempDBUsage') is null
begin
CREATE TABLE tempdb..TempDBUsage (
session_id INT, pages INT, num_reads INT, num_writes INT, login_time DATETIME, last_batch DATETIME,
cpu INT, physical_io INT, hostname NVARCHAR(64), program_name NVARCHAR(128), text NVARCHAR (MAX)
)
end
else
begin
PRINT 'To view the results run this:'
PRINT 'SELECT * FROM tempdb..TempDBUsage'
PRINT 'OR'
PRINT 'SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC'
PRINT ''
PRINT ''
PRINT 'Otherwise manually drop the table by running the following, then re-run the script:'
PRINT 'DROP TABLE tempdb..TempDBUsage'
RETURN
end
--GO
TRUNCATE TABLE tempdb..TempDBUsage
PRINT 'To view the results run this:'; PRINT 'SELECT * FROM tempdb..TempDBUsage'
PRINT 'OR'; PRINT 'SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC'
PRINT ''; PRINT ''
while @counter <= @how_many_times
begin
INSERT INTO tempdb..TempDBUsage (session_id,pages,num_reads,num_writes,login_time,last_batch,cpu,physical_io,hostname,program_name,text)
SELECT PAGES.session_id, PAGES.pages, r.num_reads, r.num_writes, sp.login_time, sp.last_batch, sp.cpu, sp.physical_io, sp.hostname, sp.program_name, t.text
FROM sys.dm_exec_connections AS r
LEFT OUTER JOIN master.sys.sysprocesses AS sp on sp.spid=r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.most_recent_sql_handle) AS t
LEFT OUTER JOIN (
SELECT s.session_id, [pages] = SUM(s.user_objects_alloc_page_count + s.internal_objects_alloc_page_count)
FROM sys.dm_db_session_space_usage AS s
GROUP BY s.session_id
HAVING SUM(s.user_objects_alloc_page_count + s.internal_objects_alloc_page_count) > 0
) PAGES ON PAGES.session_id = r.session_id
WHERE PAGES.session_id IS NOT NULL AND PAGES.pages > 50
ORDER BY PAGES.pages DESC;
PRINT CONVERT(char(10), @counter) + ': Ran at: ' + CONVERT(char(30), GETDATE())
SET @counter = @counter + 1
waitfor delay @delay_string
end
Malheureusement, le journal tempDB ne peut pas être retracé directement aux identifiants de session en affichant les processus en cours d'exécution.
Réduisez le fichier journal tempDB à un point où il augmentera à nouveau de manière significative. Créez ensuite un événement étendu pour capturer la croissance du journal. Une fois qu'il se développe à nouveau, vous pouvez développer l'événement étendu et afficher le fichier d'événements du package. Ouvrez le fichier, ajoutez un filtre temporel, un filtre de type de fichier (vous ne voulez pas que les résultats du fichier de données soient inclus), puis regroupez-le par identifiant de session dans SSMS. Cela vous aidera à trouver le (s) coupable (s) lorsque vous recherchez des identifiants de session avec le plus de groupes. Bien sûr, vous devez collecter ce qui est en cours d'exécution dans les identifiants de session via un autre processus ou outil. Peut-être que quelqu'un sait comment obtenir la requête à partir de la colonne query_hash et sera assez aimable pour publier la solution.
Résultats de l'événement étendu:
Script pour créer l'événement étendu:
CREATE EVENT SESSION [tempdb_file_size_changed] ON SERVER ADD EVENT
sqlserver.database_file_size_change(SET collect_database_name=(1)ACTION(sqlserver.client_app_name,sqlserver.client_hostname,sqlserver.is_system,sqlserver.query_hash,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.username) WHERE ([database_id]=(2))) ADD TARGETpackage0.event_file(SET filename=N'C:\ExtendedEvents\TempDBGrowth.xel',max_file_size=(100),max_rollover_files=(25)) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=1 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON)