วิธีการระบุแบบสอบถามที่เติมบันทึกธุรกรรม tempdb?


65

ฉันต้องการทราบวิธีการระบุแบบสอบถามที่ถูกต้องหรือเก็บ proc ซึ่งจริง ๆ แล้วกรอกบันทึกการทำธุรกรรมของฐานข้อมูล TEMPDB



ฉันยังใหม่กับไซต์นี้และไม่แน่ใจว่าจะแก้ไขโพสต์ได้อย่างไร ฉันไม่มีสิทธิ์เข้าถึง PROD เพื่อให้ข้อมูลเพิ่มเติม ทั้งหมดที่ฉันได้ยินจาก PROD DBA เป็นรหัสของคุณเติม tempdb! มีวิธีปฏิบัติที่ดีที่สุดในการเขียนโค้ดที่จะต้องปฏิบัติตามเพื่อให้แน่ใจว่ารหัสของเราไม่ได้เติมบันทึกของ tempdb หรือไม่?

@prasanth คุณจะต้องลงทะเบียนกับเว็บไซต์นี้ด้วย openid เดียวกันของคุณเพื่อแก้ไขคำถามของคุณที่นี่ ขึ้นอยู่กับว่าโค้ดของคุณทำอะไรเพราะเหตุใดจึงใช้ tempdb แผนการดำเนินการควรแสดงสิ่งที่กำลังทำอยู่และหากคุณโพสต์รหัสจริงเราสามารถช่วยปรับปรุงได้
เคดรูส์

@CadeRoux ฉันคิดว่าเขาพยายามระบุข้อความค้นหา (หรือข้อความค้นหา) ไม่ลองคิดดูว่าเหตุใดข้อความค้นหาเฉพาะที่รู้จักทำให้เกิดปัญหา
Aaron Bertrand

@AaronBertrand ใช่ แต่ความคิดเห็นดูเหมือนจะบ่งบอกว่าเขาต้องการแนวทางปฏิบัติที่ดีที่สุดสำหรับการเข้ารหัส
เคด Roux

คำตอบ:


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;

แก้ไข

ในขณะที่มาร์ตินชี้ให้เห็นในความคิดเห็นสิ่งนี้จะไม่พบธุรกรรมที่กำลังทำงานอยู่ซึ่งครอบครองพื้นที่ใน tempdb มันจะค้นหาเฉพาะแบบสอบถามที่ใช้งานซึ่งกำลังใช้พื้นที่อยู่ในขณะนั้น (และอาจเป็นต้นเหตุสำหรับการใช้บันทึกปัจจุบัน) ดังนั้นอาจมีธุรกรรมเปิดอยู่ แต่แบบสอบถามจริงที่ทำให้เกิดปัญหาจะไม่ทำงานอีกต่อไป

คุณสามารถเปลี่ยนinner joinบนsys.dm_exec_requestsไป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;

... จะระบุsession_ids กับธุรกรรมที่ใช้งานอยู่ซึ่งครอบครองพื้นที่บันทึก แต่คุณไม่จำเป็นต้องสามารถกำหนดคิวรีที่แท้จริงที่ทำให้เกิดปัญหาเนื่องจากถ้ามันไม่ได้ทำงานอยู่ตอนนี้มันจะไม่ถูกรวบรวมในแบบสอบถามด้านบน คำขอที่ใช้งาน คุณอาจตรวจสอบการสอบถามล่าสุดโดยใช้การโต้ตอบ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 อย่างไรและเมื่อไหร่


ขอบคุณสำหรับลิงค์ 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: พบว่ามี @@ SPID ใน cte ซึ่งจะ จำกัด ผลลัพธ์ไว้ที่เซสชันปัจจุบัน หากคุณต้องการให้มันครอบคลุมทั่วทุกเซสชั่นให้ลบออก
Ben Thul

@BenThul - ฉันเรียกใช้แบบสอบถามในการเชื่อมต่ออื่น @@SPIDเป็นไม่ได้<> รายงานสำหรับ spid ด้วยธุรกรรมเปิดสำหรับคอลัมน์ทั้งหมดสำหรับฉัน สงสัยว่าถ้าคุณต้องการสอบถามเมื่อคำขอกำลังดำเนินการจริง ๆ มากกว่าที่ไม่ได้ใช้งานกับการทำธุรกรรมเปิด =dm_db_task_space_usage0
Martin Smith

@Martin ด้วยแบบสอบถามจะค้นหาคำขอที่ใช้งานอยู่เท่านั้นไม่ใช่ธุรกรรมที่ใช้งานอยู่ ดังนั้นหากแบบสอบถามไม่ทำงานอีกต่อไปคุณพูดถูกคุณสามารถติดตามการใช้ธุรกรรม DMV ได้ แต่คุณไม่จำเป็นต้องค้นหาคิวรี่ที่ทำให้เกิดถ้ามันไม่ทำงานอีกต่อไป - สไปรต์เดียวกันนั้นอาจออกแถลงการณ์อื่น ๆ อีกหลายฉบับในธุรกรรมปัจจุบัน
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 ปรับให้เหมาะสมสำหรับการทำงานที่ยาวนานขึ้นและทำให้มันมีประโยชน์มาก แจ้งให้เราทราบหากฉันพลาดบางอย่าง แต่จนถึงตอนนี้คุณก็มีสคริปต์อัตโนมัติ / ลูป มันมีวิธีการประเมินว่าแบบสอบถาม / SPID เป็นผู้กระทำความผิดในช่วงระยะเวลาหนึ่งโดยใช้แบบสอบถามค่าเบี่ยงเบนมาตรฐาน (STDEV) ด้านล่าง

สิ่งนี้จะวิ่งทุกๆ 3 นาทีเป็นเวลา 40 ครั้งดังนั้น 2 ชั่วโมง ปรับเปลี่ยนพารามิเตอร์ตามที่เห็นสมควร

มีตัวกรองที่> 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 Agent จะยังคงทำงานต่อไปแม้ว่า SSMS จะถูกปิด ขอบคุณสำหรับการแบ่งปัน!
Lockszmith

1

น่าเสียดายที่บันทึก tempDB ไม่สามารถติดตามกลับไปที่ sessionID โดยตรงได้โดยดูกระบวนการที่กำลังทำงานอยู่

ลดขนาดไฟล์บันทึก tempDB ไปยังจุดที่มันจะเติบโตอย่างมีนัยสำคัญอีกครั้ง จากนั้นสร้างเหตุการณ์เพิ่มเติมเพื่อบันทึกการเติบโตของบันทึก เมื่อมันเติบโตขึ้นอีกครั้งคุณสามารถขยายเหตุการณ์เพิ่มเติมและดูไฟล์เหตุการณ์แพคเกจ เปิดไฟล์เพิ่มตัวกรองเวลาตัวกรองประเภทไฟล์ (คุณไม่ต้องการให้มีไฟล์ข้อมูลผลลัพธ์รวมอยู่) จากนั้นจัดกลุ่มตามรหัสเซสชันใน SSMS วิธีนี้จะช่วยให้คุณพบผู้กระทำผิดขณะที่คุณกำลังมองหารหัสเซสชันที่มีกลุ่มมากที่สุดโดย แน่นอนคุณต้องรวบรวมสิ่งที่กำลังทำงานอยู่ในเซสชั่น 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.