Làm cách nào để xác định truy vấn nào đang điền vào nhật ký giao dịch tempdb?


65

Tôi muốn biết làm thế nào để xác định chính xác truy vấn hoặc lưu trữ Proc thực sự đang điền vào nhật ký giao dịch của cơ sở dữ liệu TEMPDB.



Tôi mới đến trang web này và không biết làm thế nào để chỉnh sửa bài viết. Tôi không có quyền truy cập vào SẢN PHẨM để cung cấp thêm thông tin. Tất cả những gì tôi nghe được từ SẢN PHẨM DBA là mã của bạn đang điền tempdb! Có bất kỳ thực hành mã hóa tốt nhất nào cần được tuân theo để đảm bảo mã của chúng tôi không điền vào nhật ký của tempdb không?

@prasanth Bạn sẽ cần phải đăng ký trang web này với cùng một openid của bạn để thực hiện các thay đổi cho câu hỏi của bạn ở đây. Nó phụ thuộc vào những gì mã của bạn đang làm như lý do tại sao nó đang sử dụng tempdb. Kế hoạch thực hiện sẽ hiển thị những gì nó đang làm và nếu bạn đăng mã thực tế, chúng tôi có thể giúp cải thiện nó.
Cade Roux

@CadeRoux Tôi nghĩ rằng anh ta đang cố gắng xác định truy vấn (hoặc truy vấn), không cố gắng tìm hiểu tại sao một truy vấn cụ thể, đã biết lại gây ra sự cố.
Aaron Bertrand

@AaronBertrand yeah, nhưng bình luận dường như cho thấy anh ấy muốn thực hành tốt nhất cho tiền mã hóa.
Cade Roux

Câu trả lời:


73

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

BIÊN TẬP

Như Martin đã chỉ ra trong một bình luận, điều này sẽ không tìm thấy các giao dịch đang hoạt động đang chiếm dung lượng trong tempdb, nó sẽ chỉ tìm thấy các truy vấn đang sử dụng không gian ở đó (và có thể là thủ phạm cho việc sử dụng nhật ký hiện tại). Vì vậy, có thể có một giao dịch mở nhưng truy vấn thực tế gây ra sự cố không còn chạy nữa.

Bạn có thể thay đổi inner joinbật sys.dm_exec_requeststhành a left outer join, sau đó bạn sẽ trả lại các hàng cho các phiên hiện không chủ động chạy truy vấn.

Truy vấn Martin đã đăng ...

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;

... sẽ xác định session_ids với các giao dịch đang hoạt động đang chiếm không gian nhật ký, nhưng bạn không nhất thiết có thể xác định truy vấn thực sự gây ra sự cố, vì nếu bây giờ nó không chạy thì sẽ không bị bắt trong truy vấn trên yêu cầu hoạt động. Bạn có thể kiểm tra một cách phản ứng truy vấn gần đây nhất bằng cách sử dụng DBCC INPUTBUFFERnhưng nó có thể không cho bạn biết những gì bạn muốn nghe. Bạn có thể tham gia bên ngoài theo cách tương tự để bắt những người tích cực chạy, ví dụ:

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;

Bạn cũng có thể sử dụng DMV sys.dm_db_session_space_usageđể xem việc sử dụng không gian tổng thể theo phiên (nhưng một lần nữa bạn có thể không nhận lại kết quả hợp lệ cho truy vấn; nếu truy vấn không hoạt động, những gì bạn nhận lại có thể không phải là thủ phạm thực sự).

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

Với tất cả các truy vấn này theo ý của bạn, bạn sẽ có thể thu hẹp ai đang sử dụng tempdb và cách thức, đặc biệt là nếu bạn bắt chúng trong hành động.

Một số mẹo để giảm thiểu việc sử dụng tempdb

  1. sử dụng ít bảng #temp và biến @table
  2. giảm thiểu bảo trì chỉ mục đồng thời và tránh SORT_IN_TEMPDBtùy chọn nếu không cần thiết
  3. tránh những con trỏ không cần thiết; tránh các con trỏ tĩnh nếu bạn nghĩ rằng đây có thể là một nút cổ chai, vì các con trỏ tĩnh sử dụng các bảng công việc trong tempdb - mặc dù đây là loại con trỏ tôi luôn khuyên dùng nếu tempdb không phải là nút cổ chai
  4. cố gắng tránh các cuộn chỉ (ví dụ: các CTE lớn được tham chiếu nhiều lần trong truy vấn)
  5. không sử dụng MARS
  6. kiểm tra kỹ lưỡng việc sử dụng các mức cô lập snapshot / RCSI - đừng chỉ bật nó cho tất cả các cơ sở dữ liệu vì bạn đã nói rằng nó tốt hơn NOLOCK (nhưng nó không miễn phí)
  7. trong một số trường hợp, nó có vẻ không trực quan, nhưng sử dụng nhiều bảng tạm thời hơn . ví dụ: chia một truy vấn khiêm tốn thành các phần có thể kém hiệu quả hơn một chút, nhưng nếu nó có thể tránh được sự cố tràn bộ nhớ lớn đến tempdb vì truy vấn đơn, lớn hơn yêu cầu cấp bộ nhớ quá lớn ...
  8. tránh kích hoạt các hoạt động hàng loạt
  9. tránh lạm dụng các loại LOB (loại tối đa, XML, v.v.) làm biến cục bộ
  10. giữ giao dịch ngắn và ngọt ngào
  11. không đặt tempdb làm cơ sở dữ liệu mặc định của mọi người -

Bạn cũng có thể xem xét rằng việc sử dụng nhật ký tempdb của bạn có thể do các quy trình nội bộ mà bạn có ít hoặc không kiểm soát được - ví dụ: thư cơ sở dữ liệu, thông báo sự kiện, thông báo truy vấn và nhà môi giới dịch vụ đều sử dụng tempdb theo một cách nào đó. Bạn có thể ngừng sử dụng các tính năng này, nhưng nếu bạn đang sử dụng chúng, bạn không thể ra lệnh làm thế nào và khi nào họ sử dụng tempdb.


Cảm ơn liên kết Aaron. Nói chung, có bất kỳ thực hành mã hóa tốt nhất nào cần được tuân theo để tránh điền vào nhật ký giao dịch TEMPDB không?

2
Hmm, chỉ cần kiểm tra điều đó và nó đã không tìm thấy phiên vi phạm của tôi mặc dù session_idhiển thị với truy vấn sau đây 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. Truy vấn mà tôi mong đợi tìm thấy là sau khi chạy như sauBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith

@Martin: Nhận thấy có @@ SPID trong cte, điều này sẽ giới hạn kết quả trong phiên hiện tại. Nếu bạn muốn nó trải dài trên tất cả các phiên, hãy loại bỏ nó.
Ben Thul

@BenThul - Tôi đã chạy truy vấn trong một kết nối khác. Các @@SPID<>không =. dm_db_task_space_usagebáo cáo 0cho spid với giao dịch mở cho tất cả các cột cho tôi. Tự hỏi nếu bạn cần truy vấn nó khi yêu cầu thực sự được thực thi thay vì nhàn rỗi với một giao dịch mở.
Martin Smith

@MartinSmith truy vấn chỉ tìm thấy các yêu cầu hoạt động, không phải giao dịch hoạt động. Vì vậy, nếu truy vấn không còn chạy nữa, bạn đã đúng, bạn có thể theo dõi lại bằng DMV giao dịch. Nhưng bạn không nhất thiết có thể tìm ra truy vấn gây ra nó nếu nó không còn chạy nữa - chính điều đó có thể đã đưa ra một số tuyên bố khác trong giao dịch hiện tại.
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

 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

Cảm ơn bạn cho bài viết này, có lẽ là người duy nhất của loại hình này. Thử nghiệm của tôi rất đơn giản, tạo một bảng tạm thời và đảm bảo nó hiển thị khi tôi chạy bất kỳ truy vấn nào từ bài đăng này ... chỉ một hoặc hai thực sự thành công. Tôi đã sửa nó để tham gia vào T-SQL, tối ưu hóa nó để chạy lâu hơn và làm cho nó khá hữu ích. Hãy cho tôi biết nếu tôi bỏ lỡ điều gì đó nhưng cho đến nay bạn đã có một tập lệnh tự động / lặp. Nó cung cấp cách đánh giá truy vấn / SPID nào là người phạm tội trong một khoảng thời gian bằng cách sử dụng truy vấn độ lệch chuẩn (STDEV) bên dưới.

Điều này chạy cứ sau 3 phút trong 40 lần, vì vậy 2 giờ. Sửa đổi các thông số khi bạn thấy phù hợp.

Có bộ lọc WHERE> 50 trang bên dưới mà mọi người có thể muốn xóa chỉ trong trường hợp bạn có nhiều bảng nhỏ. Nếu không, bạn sẽ không nắm bắt được sắc thái đó với bên dưới vì nó là ...

Thưởng thức!

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

Kết hợp điều này với câu trả lời được chấp nhận là một cách thuận tiện để theo dõi hoạt động tempdb đang trốn tránh. Chạy nó thông qua một tác vụ theo lịch trình của Tác nhân SQL sẽ giữ cho nó chạy ngay cả khi SSMS bị đóng. Cám ơn vì đã chia sẻ!
Lockzmith

1

Thật không may, nhật ký tempDB không thể được truy nguyên trực tiếp trở lại sessionID bằng cách xem các quy trình đang chạy.

Thu nhỏ tệp nhật ký tempDB đến một điểm mà nó sẽ tăng trưởng trở lại đáng kể. Sau đó tạo một sự kiện mở rộng để nắm bắt sự tăng trưởng của nhật ký. Khi nó phát triển trở lại, bạn có thể mở rộng sự kiện mở rộng và xem tệp sự kiện gói. Mở tệp, thêm bộ lọc thời gian, bộ lọc loại tệp (bạn không muốn có kết quả tệp dữ liệu), sau đó nhóm nó theo id phiên trong SSMS. Điều này sẽ giúp bạn tìm ra thủ phạm khi bạn đang tìm kiếm id phiên với nhiều nhóm nhất. Tất nhiên, bạn cần thu thập những gì đang chạy trong phiên id thông qua một quy trình hoặc công cụ khác. Có lẽ ai đó biết cách lấy truy vấn từ cột query_hash và sẽ đủ tử tế để đăng giải pháp.

Kết quả của sự kiện mở rộng:

nhập mô tả hình ảnh ở đây

Kịch bản để tạo sự kiện mở rộng:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.