Bagaimana cara mengidentifikasi kueri mana yang mengisi log transaksi tempdb?

65

Saya ingin tahu cara mengidentifikasi permintaan yang tepat atau simpanan tersimpan yang sebenarnya mengisi log transaksional dari basis data TEMPDB.

prasanth
sumber
Saya baru ke situs ini dan tidak yakin bagaimana mengedit posting. Saya tidak memiliki akses ke PROD untuk memberikan info lebih lanjut. Yang saya dengar dari PROD DBA adalah kode Anda mengisi tempdb! Apakah ada praktik terbaik pengkodean yang harus diikuti untuk memastikan kode kami tidak mengisi log tempdb?
@prasanth Anda harus mendaftar untuk situs ini dengan openid yang sama untuk mengubah pertanyaan Anda di sini. Tergantung pada apa kode Anda lakukan mengapa menggunakan tempdb. Rencana pelaksanaan harus menunjukkan apa yang dilakukannya, dan jika Anda memposting kode yang sebenarnya, kami dapat membantu memperbaikinya.
Cade Roux
@CadeRoux Saya pikir dia mencoba mengidentifikasi kueri (atau kueri), tidak mencoba mencari tahu mengapa kueri tertentu yang diketahui menyebabkan masalah.
Aaron Bertrand
@ AaronBertrand ya, tetapi komentar tersebut sepertinya mengindikasikan ia menginginkan praktik terbaik untuk pengkodean.
Cade Roux

Jawaban:

73

Dari 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;

SUNTING

Seperti yang ditunjukkan Martin dalam komentar, ini tidak akan menemukan transaksi aktif yang menempati ruang di tempdb, itu hanya akan menemukan permintaan aktif yang saat ini menggunakan ruang di sana (dan kemungkinan penyebab untuk penggunaan log saat ini). Jadi mungkin ada transaksi terbuka tetapi permintaan sebenarnya yang menyebabkan masalah tidak lagi berjalan.

Anda bisa mengubah inner joinpada sys.dm_exec_requestske left outer join, maka Anda akan kembali baris untuk sesi yang saat ini tidak aktif menjalankan query.

Permintaan Martin diposting ...

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;

... akan mengidentifikasi session_iddengan transaksi aktif yang menempati ruang log, tetapi Anda tidak akan selalu dapat menentukan permintaan aktual yang menyebabkan masalah, karena jika itu tidak berjalan sekarang tidak akan ditangkap dalam permintaan di atas untuk permintaan aktif. Anda mungkin dapat memeriksa secara reaktif kueri terkini menggunakan DBCC INPUTBUFFERtetapi itu mungkin tidak memberi tahu Anda apa yang ingin Anda dengar. Anda dapat bergabung dengan luar dengan cara yang sama untuk menangkap mereka yang aktif berjalan, misalnya:

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;

Anda juga dapat menggunakan DMV sys.dm_db_session_space_usageuntuk melihat pemanfaatan ruang secara keseluruhan berdasarkan sesi (tetapi sekali lagi Anda mungkin tidak mendapatkan kembali hasil yang valid untuk kueri; jika kueri tidak aktif, apa yang Anda dapatkan mungkin bukan penyebab sebenarnya).

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

Dengan semua pertanyaan ini yang Anda inginkan, Anda harus dapat mempersempit siapa yang menggunakan tempdb dan bagaimana, terutama jika Anda menangkapnya saat beraksi.

beberapa tips untuk meminimalkan pemanfaatan tempdb

  1. gunakan lebih sedikit tabel #temp dan variabel @table
  2. meminimalkan pemeliharaan indeks bersamaan, dan hindari SORT_IN_TEMPDBopsi jika tidak diperlukan
  3. menghindari kursor yang tidak perlu; hindari kursor statis jika Anda pikir ini mungkin menjadi hambatan, karena kursor statis menggunakan tabel kerja di tempdb - meskipun ini adalah tipe kursor yang selalu saya sarankan jika tempdb bukan hambatan
  4. coba hindari gulungan (mis. CTE besar yang direferensikan beberapa kali dalam kueri)
  5. jangan gunakan MARS
  6. benar-benar menguji penggunaan tingkat isolasi snapshot / RCSI - jangan hanya menyalakannya untuk semua database karena Anda telah diberitahu itu lebih baik daripada NOLOCK (memang, tapi itu tidak gratis)
  7. dalam beberapa kasus, mungkin terdengar tidak intuitif, tetapi gunakan lebih banyak tabel temp. misalnya, memecah permintaan yang besar menjadi beberapa bagian mungkin sedikit kurang efisien, tetapi jika itu dapat menghindari tumpahan memori yang sangat besar untuk tempdb karena permintaan tunggal yang lebih besar memerlukan hibah memori terlalu besar ...
  8. hindari mengaktifkan pemicu untuk operasi massal
  9. hindari penggunaan berlebihan tipe LOB (tipe maks, XML, dll) sebagai variabel lokal
  10. pertahankan transaksi singkat dan manis
  11. jangan set tempdb menjadi database default semua orang -

Anda juga dapat mempertimbangkan bahwa penggunaan log tempdb Anda mungkin disebabkan oleh proses internal yang Anda sedikit atau tidak memiliki kontrol atas - misalnya surat basis data, pemberitahuan acara, pemberitahuan permintaan dan broker layanan semua menggunakan tempdb dalam beberapa cara. Anda dapat berhenti menggunakan fitur-fitur ini, tetapi jika Anda menggunakannya, Anda tidak dapat menentukan bagaimana dan kapan mereka menggunakan tempdb.

Aaron Bertrand
sumber
Terima kasih untuk tautannya Aaron. Secara umum apakah ada praktik terbaik pengkodean yang perlu diikuti untuk menghindari pengisian log Transaksional TEMPDB?
2
Hmm, Baru saja mengujinya dan belum menemukan sesi menyinggung saya meskipun session_idmuncul dengan permintaan berikut 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. Permintaan yang saya harapkan akan ditemukan adalah setelah menjalankan yang berikutBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith
@ Martin: Melihat ada @@ SPID di cte, yang akan membatasi hasil untuk sesi saat ini. Jika Anda ingin menjangkau semua sesi, hapus itu.
Ben Thul
@ BenTul - Saya menjalankan kueri di koneksi lain. The @@SPIDadalah <>tidak =. dm_db_task_space_usagelaporan 0untuk spid dengan transaksi terbuka untuk semua kolom untuk saya. Bertanya-tanya apakah Anda perlu menanyakannya ketika permintaan benar-benar mengeksekusi daripada menganggur dengan transaksi terbuka.
Martin Smith
@MartinSmith kueri hanya menemukan permintaan aktif, bukan transaksi aktif. Jadi, jika kueri tidak lagi berjalan, Anda benar, Anda dapat melacak kembali menggunakan DMV transaksi. Tapi Anda tidak akan bisa mengetahui kueri yang menyebabkannya jika tidak lagi berjalan - spid yang sama mungkin telah mengeluarkan beberapa pernyataan lain dalam transaksi saat ini.
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
Saurabh Sinha
sumber
4

Terima kasih untuk posting ini, mungkin satu-satunya dari jenisnya. Pengujian saya sederhana, buat tabel temp dan pastikan itu muncul ketika saya menjalankan salah satu pertanyaan dari posting ini ... hanya satu atau dua yang benar-benar berhasil. Saya memperbaikinya untuk bergabung dengan T-SQL, dioptimalkan untuk berjalan lebih lama dan membuatnya sangat berguna. Beri tahu saya jika saya melewatkan sesuatu tetapi sejauh ini Anda mendapatkan skrip otomatis / berulang. Ini memberikan cara menilai kueri / SPID mana yang merupakan pelaku selama periode waktu tertentu dengan menggunakan kueri deviasi standar (STDEV) di bawah ini.

Ini berjalan setiap 3 menit selama 40 kali, jadi 2 jam. Ubah parameter sesuai keinginan Anda.

Ada WHERE> 50 halaman filter di bawah ini yang orang mungkin ingin menghapus kalau-kalau Anda memiliki banyak tabel kecil. Kalau tidak, Anda tidak akan menangkap nuansa itu dengan ...

Nikmati!

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
Joe Zee
sumber
Menggabungkan ini dengan jawaban yang diterima adalah cara mudah untuk melacak aktivitas tempdb yang mengelak. Menjalankan ini melalui tugas yang dijadwalkan Agen SQL akan membuat ini berjalan bahkan jika SSMS ditutup. Terima kasih telah berbagi!
Lockszmith
1

Sayangnya log tempDB tidak dapat dilacak langsung ke sessionID dengan melihat proses yang sedang berjalan.

Kecilkan file log tempDB ke titik di mana ia akan tumbuh secara signifikan lagi. Kemudian buat acara diperpanjang untuk menangkap pertumbuhan log. Setelah itu tumbuh lagi Anda dapat memperluas acara yang diperluas dan melihat file paket acara. Buka file, tambahkan filter waktu, filter tipe file (Anda tidak ingin hasil file data dimasukkan), dan kemudian kelompokkan dengan id sesi di SSMS. Ini akan membantu Anda menemukan pelakunya saat Anda mencari id sesi dengan kelompok terbanyak oleh. Tentu saja Anda perlu mengumpulkan apa yang sedang berjalan di sesi id melalui proses atau alat lain. Mungkin seseorang tahu cara mendapatkan kueri dari kolom query_hash dan akan berbaik hati untuk mengirim solusinya.

Hasil acara diperpanjang:

masukkan deskripsi gambar di sini

Script untuk membuat acara yang diperluas:

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)
Tequila
sumber