tempdb 트랜잭션 로그를 채우는 쿼리를 식별하는 방법은 무엇입니까?


65

TEMPDB 데이터베이스의 트랜잭션 로그를 실제로 채우는 정확한 쿼리 또는 저장된 proc을 식별하는 방법을 알고 싶습니다.


4
MSDN의 tempdb 에서 디스크 공간 부족 문제 해결을 참조하십시오 .
David Brabant

이 사이트를 처음 사용하며 게시물을 편집하는 방법을 잘 모르겠습니다. 더 많은 정보를 제공하기 위해 PROD에 액세스 할 수 없습니다. 내가 PROD DBA에서 듣고있는 것은 코드가 tempdb를 채우고 있다는 것입니다! 코드가 tempdb의 로그를 채우지 않도록하기 위해 따라야 할 코딩 모범 사례가 있습니까?

@prasanth 여기에서 질문을 변경하려면 동일한 openid로이 사이트에 가입해야합니다. tempdb를 사용하는 이유는 코드에서 수행하는 작업에 따라 다릅니다. 실행 계획에는 수행중인 작업이 표시되어야하며 실제 코드를 게시하면 개선 할 수 있습니다.
Cade Roux

@CadeRoux 나는 그가 알려진 특정 쿼리가 문제를 일으키는 이유를 파악하려고 시도하지 않고 쿼리 (또는 쿼리)를 식별하려고한다고 생각합니다.
Aaron Bertrand

@AaronBertrand 예, 그러나 주석은 코딩에 대한 모범 사례를 원한다고 나타냅니다.
Cade Roux 2016 년

답변:


73

에서 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;

편집하다

Martin은 주석에서 지적했듯이 tempdb에서 공간을 차지하는 활성 트랜잭션 을 찾지 못하고 현재 공간을 사용하는 활성 쿼리 만 찾습니다 (현재 로그 사용에 대한 범인 일 가능성이 있음). 따라서 열린 트랜잭션이있을 수 있지만 문제를 일으키는 실제 쿼리는 더 이상 실행되지 않습니다.

당신은 변경할 수 inner joinsys.dm_exec_requestsA를 left outer join다음 현재 적극적으로 쿼리를 실행하지 않는 세션에 대한 행을 반환합니다.

검색어 Martin이 게시했습니다 ...

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;

... session_id로그 공간을 차지하는 활성 트랜잭션 이있는을 식별 할 수 있지만 문제점을 발생시킨 실제 쿼리를 반드시 판별 할 수있는 것은 아닙니다. 지금 실행되지 않으면 위의 쿼리에서 캡처되지 않기 때문입니다. 활성 요청. 다음을 사용하여 가장 최근의 쿼리를 반응 적으로 확인할 수 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;

또한 DMV sys.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;

이러한 모든 쿼리를 마음대로 사용할 수 있으므로 tempdb를 누가 사용하고 있는지, 특히 행위에서 쿼리를 잡는 경우 방법을 좁힐 수 있습니다.

tempdb 사용을 최소화하기위한 몇 가지 팁

  1. 적은 #temp 테이블과 @table 변수를 사용하십시오.
  2. 동시 인덱스 유지 관리를 최소화하고 SORT_IN_TEMPDB필요하지 않은 옵션은 피하십시오
  3. 불필요한 커서를 피하십시오. 정적 커서는 tempdb에서 작업 테이블을 사용하기 때문에 병목 현상이 발생할 수 있다고 생각되면 정적 커서를 피하십시오-이것은 커서 유형이지만 tempdb가 병목 현상이 아닌 경우 항상 권장합니다
  4. 스풀을 피하십시오 (예 : 쿼리에서 여러 번 참조되는 큰 CTE)
  5. MARS를 사용하지 마십시오
  6. 스냅 샷 / RCSI 격리 수준의 사용을 철저히 테스트하십시오. NOLOCK보다 낫다는 말을 들었으므로 모든 데이터베이스에 대해 설정하지 마십시오 (그렇지만 무료는 아님).
  7. 경우에 따라 직관적이지 않은 것처럼 들릴 수도 있지만 임시 테이블을 더 많이 사용 합니다. 예를 들어, 엄청난 쿼리를 부분으로 나누는 것이 약간 덜 효율적일 수 있지만, 단일의 큰 쿼리에 너무 큰 메모리 부여가 필요하기 때문에 tempdb에 엄청난 메모리 유출을 피할 수 있다면 ...
  8. 대량 작업에 트리거를 사용하지 마십시오.
  9. LOB 유형 (최대 유형, XML 등)을 로컬 변수로 과도하게 사용하지 마십시오.
  10. 짧고 달콤한 거래를 유지
  11. tempdb를 모든 사람의 기본 데이터베이스로 설정하지 마십시오.

tempdb 로그 사용은 데이터베이스 메일, 이벤트 알림, 쿼리 알림 및 서비스 브로커와 같은 거의 제어 할 수없는 내부 프로세스로 인해 발생할 수 있다고 생각할 수 있습니다. 이러한 기능의 사용을 중지 할 수 있지만 사용중인 경우 tempdb를 사용하는 방법과시기를 지정할 수 없습니다.


링크 Aaron에게 감사합니다. 일반적으로 TEMPDB 트랜잭션 로그가 가득 차지 않도록 따라야하는 코딩 모범 사례가 있습니까?

2
흠, 그냥 테스트 한 결과 session_id다음 쿼리와 함께 표시 되지만 내 위반 세션을 찾지 못했습니다 SELECT database_transaction_log_bytes_reserved,session_id FROM sys.dm_tran_database_transactions tdt JOIN sys.dm_tran_session_transactions tst ON tdt.transaction_id = tst.transaction_id WHERE database_id = 2. 내가 찾은 쿼리는 다음을 실행 한 후였습니다BEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith

@Martin : cte에 @@ SPID가있어 결과를 현재 세션으로 제한합니다. 모든 세션에 걸쳐 적용하려면 제거하십시오.
벤 툴

@ BenThul-다른 연결에서 쿼리를 실행했습니다. 는 @@SPID것입니다 <>하지 =. 나를 위해 모든 열에 대한 공개 트랜잭션이있는 spid에 대한 dm_db_task_space_usage보고서 0. 오픈 트랜잭션으로 유휴 상태가 아닌 요청이 실제로 실행될 때 쿼리해야하는지 궁금합니다.
Martin Smith

@MartinSmith 쿼리는 활성 트랜잭션이 아닌 활성 요청 만 찾습니다. 따라서 쿼리가 더 이상 실행되지 않으면 맞습니다. 트랜잭션 DMV를 사용하여 추적 할 수 있습니다. 그러나 더 이상 실행되지 않는 경우 쿼리를 발생시킨 쿼리를 반드시 파악할 수있는 것은 아닙니다. 동일한 spid가 현재 트랜잭션에서 다른 여러 명령문을 발행했을 수 있습니다.
Aaron Bertrand

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

4

이 게시물에 대해 감사합니다. 아마 유일한 종류 일 것입니다. 내 테스트는 간단하고 임시 테이블을 만들고이 게시물의 쿼리를 실행할 때 표시되는지 확인하십시오. 한두 가지만 실제로 성공했습니다. T-SQL에 가입하도록 수정하고 더 오래 실행되도록 최적화했으며 매우 유용했습니다. 내가 놓친 것이 있으면 알려주세요. 지금까지 자동화 된 / 루프 된 스크립트가 있습니다. 아래 표준 편차 (STDEV) 쿼리를 사용하여 일정 기간 동안 어떤 쿼리 / SPID가 가해자인지 평가하는 방법을 제공합니다.

이것은 3 분마다 40 번씩 실행되므로 2 시간이 걸립니다. 원하는대로 매개 변수를 수정하십시오.

작은 테이블이 많은 경우에 대비하여 사람들이 지우고 싶을 수있는 50 페이지보다 큰 WHERE 필터가 있습니다. 그렇지 않으면 아래 내용을 그대로 뉘앙스로 잡을 수 없습니다.

즐겨!

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

이것을 허용 된 답변과 결합하면 tempdb 활동을 피하는 편리한 방법입니다. SQL 에이전트 예약 작업을 통해이 작업을 실행하면 SSMS가 닫혀 있어도 계속 실행됩니다. 공유해 주셔서 감사합니다!
Lockszmith

1

불행히도 tempDB 로그는 실행중인 프로세스를 보면서 sessionID로 직접 추적 할 수 없습니다.

tempDB 로그 파일을 다시 크게 커질 지점으로 줄입니다. 그런 다음 확장 이벤트를 작성하여 로그 증가를 캡처하십시오. 다시 커지면 확장 이벤트를 확장하고 패키지 이벤트 파일을 볼 수 있습니다. 파일을 열고 시간 필터, 파일 형식 필터 (데이터 파일 결과가 포함되지 않도록 함)를 추가 한 다음 SSMS에서 세션 ID별로 그룹화하십시오. 이렇게하면 그룹별로 가장 많은 세션 ID를 찾을 때 범인을 찾는 데 도움이됩니다. 물론 다른 프로세스 나 도구를 통해 세션 ID에서 실행중인 것을 수집해야합니다. 어쩌면 누군가 query_hash 열에서 쿼리를 얻는 방법을 알고 솔루션을 게시하기에 충분할 것입니다.

확장 된 이벤트 결과 :

여기에 이미지 설명을 입력하십시오

확장 이벤트를 작성하는 스크립트 :

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.