どのクエリがtempdbトランザクションログを埋めているのかを識別する方法


65

TEMPDBデータベースのトランザクションログを実際に埋めている正確なクエリまたはストアドプロシージャを識別する方法を知りたいです。



このサイトは初めてで、投稿の編集方法がわかりません。詳細情報を提供するためにPRODにアクセスできません。PROD DBAから聞いているのは、コードがtempdbを埋めていることだけです!コードがtempdbのログをいっぱいにしないようにするために、コーディングのベストプラクティスに従う必要がありますか?

@prasanthここで質問を変更するには、同じopenidでこのサイトにサインアップする必要があります。tempdbを使用している理由については、コードの実行内容に依存します。実行計画はそれが何をしているのかを示す必要があります。実際のコードを投稿すると、改善に役立ちます。
ケイドルー

@CadeRouxは、特定の既知のクエリが問題を引き起こしている理由を解明しようとせず、クエリを特定しようとしていると思います。
アーロンバートランド

@AaronBertrandええ、しかしコメントは彼がコーディングのベストプラクティスを望んでいることを示しているようです。
ケードルー

回答:


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 joinon sys.dm_exec_requestsをaに変更すると、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ログスペースを占有しているアクティブなトランザクションで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;

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を使用します。これらの機能の使用を停止できますが、それらを使用している場合、tempdbを使用する方法とタイミングを指定することはできません。


リンクアーロンに感謝します。一般に、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:cteに@@ SPIDがあることに注意してください。これは、結果を現在のセッションに制限します。すべてのセッションにまたがる場合は、それを削除します。
ベンスル

@BenThul-別の接続でクエリを実行しました。で@@SPID<>ない=。すべての列のオープントランザクションを含むspidのdm_db_task_space_usageレポート0。開いているトランザクションでアイドル状態ではなく、要求が実際に実行されているときにクエリを実行する必要がある場合は、疑問に思います。
マーティンスミス

@MartinSmithクエリはアクティブなリクエストのみを検索し、アクティブなトランザクションは検索しません。そのため、クエリが実行されていない場合は、トランザクションDMVを使用して追跡できます。ただし、実行されていない場合にクエリの原因となったクエリを必ずしも把握できるとは限りません。同じspidが現在のトランザクションで他のいくつかのステートメントを発行した可能性があります。
アーロンバートランド

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

たぶんその種の唯一のものであるこの投稿をありがとう。私のテストは簡単で、一時テーブルを作成し、この投稿からクエリを実行したときに表示されることを確認しました。本当に成功したのは1つまたは2つだけです。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

これを受け入れられた回答と組み合わせることは、回避するtempdbアクティビティを追跡する便利な方法です。SQLエージェントのスケジュールされたタスクを介してこれを実行すると、SSMSが閉じられていても実行され続けます。共有してくれてありがとう!
Lockszmith

1

残念ながら、実行中のプロセスを表示しても、tempDBログをセッションIDに直接追跡することはできません。

tempDBログファイルを、再び大きく成長するポイントまで圧縮します。次に、拡張イベントを作成して、ログの増加をキャプチャします。再び大きくなったら、拡張イベントを展開してパッケージイベントファイルを表示できます。ファイルを開き、時間フィルター、ファイルタイプフィルターを追加し(データファイルの結果を含めたくない)、SSMSのセッションIDでグループ化します。これは、最も多くのgroup byのセッション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.