如何确定哪个查询正在填满 tempdb 事务日志?

pra*_*nth 75 sql-server-2005 sql-server-2008 sql-server tempdb transaction-log

我想知道如何识别实际填充 TEMPDB 数据库事务日志的确切查询或存储过程。

Aar*_*and 80

来自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;
Run Code Online (Sandbox Code Playgroud)

编辑

正如 Martin 在评论中指出的那样,这不会找到占用 tempdb 空间的活动事务,它只会找到当前正在使用那里空间的活动查询(并且可能是当前日志使用的罪魁祸首)。所以可能有一个打开的事务,但导致问题的实际查询不再运行。

您可以将inner joinon更改sys.dm_exec_requests为 a left outer join,然后您将为当前未主动运行查询的会话返回行。

马丁发布的查询...

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;
Run Code Online (Sandbox Code Playgroud)

...将session_id使用占用日志空间的活动事务识别s,但您不一定能够确定导致问题的实际查询,因为如果它现在没有运行,则不会在上述查询中捕获主动请求。您可以使用DBCC INPUTBUFFER它来被动地检查最近的查询,但它可能不会告诉您想要听到的内容。您可以以类似的方式外连接来捕获那些主动运行的,例如:

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;
Run Code Online (Sandbox Code Playgroud)

您还可以使用 DMVsys.dm_db_session_space_usage查看会话的总体空间利用率(但同样,您可能无法返回查询的有效结果;如果查询未处于活动状态,您返回的内容可能不是真正的罪魁祸首)。

;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;
Run Code Online (Sandbox Code Playgroud)

有了所有这些查询供您使用,您应该能够缩小谁在使用 tempdb 以及如何使用的范围,特别是如果您在行动中发现了它们。

最小化 tempdb 利用率的一些技巧

  1. 使用更少的#temp 表和@table 变量
  2. 最小化并发索引维护,并在SORT_IN_TEMPDB不需要时避免该选项
  3. 避免不必要的游标;如果您认为这可能是瓶颈,请避免使用静态游标,因为静态游标使用 tempdb 中的工作表
  4. 尽量避免假脱机(例如在查询中多次引用的大型 CTE)
  5. 不要使用火星
  6. 彻底测试快照/RCSI 隔离级别的使用 - 不要只是为所有数据库打开它,因为你被告知它比 NOLOCK 更好(它是,但它不是免费的)
  7. 在某些情况下,这听起来可能不直观,但可以使用更多的临时表。例如,将一个庞大的查询分成几部分可能效率稍低,但如果它可以避免大量内存溢出到 tempdb,因为单个较大的查询需要太大的内存授权......
  8. 避免为批量操作启用触发器
  9. 避免过度使用 LOB 类型(最大类型、XML 等)作为局部变量
  10. 保持交易简短而甜蜜
  11. 不要将 tempdb 设置为每个人的默认数据库 -

您可能还认为您的 tempdb 日志使用可能是由您几乎无法控制或无法控制的内部进程引起的 - 例如,数据库邮件、事件通知、查询通知和服务代理都以某种方式使用 tempdb。您可以停止使用这些功能,但如果您正在使用它们,则无法规定它们如何以及何时使用 tempdb。


Sau*_*nha 5

https://social.msdn.microsoft.com/Forums/sqlserver/en-US/17d9f862-b9ae-42de-ada0-4229f56712dc/tempdb-log-filling-cannot-find-how-or-what?forum=sqldatabaseengine

 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
Run Code Online (Sandbox Code Playgroud)


Joe*_*Zee 5

感谢您的这篇文章,可能是同类文章中唯一的一篇。我的测试很简单,创建一个临时表并确保当我运行本文中的任何查询时它都会显示......只有一两个真正成功。我更正了它以加入 T-SQL,优化了它以实现更长的运行,并使其非常有用。如果我错过了什么,请告诉我,但到目前为止,您已经得到了一个自动/循环脚本。它提供了一种通过使用下面的标准偏差 (STDEV) 查询来评估在一段时间内哪个查询/SPID 是违规者的方法。

每 3 分钟运行一次,共运行 40 次,即 2 小时。根据需要修改参数。

下面有一个 WHERE > 50 页过滤器,人们可能需要清除该过滤器,以防万一您有很多小表。否则你将无法理解下面的细微差别......

享受!

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
Run Code Online (Sandbox Code Playgroud)