วิธีค้นหาคิวรี่ที่ยังคงล็อคอยู่


15

การสืบค้นsys.dm_tran_locksDMV แสดงให้เราเห็นว่าเซสชันใด (SPIDs) กำลังล็อคทรัพยากรเช่นตารางหน้าและแถว

สำหรับการล็อคแต่ละครั้งที่ได้มามีวิธีใดบ้างที่จะตัดสินว่าคำสั่ง SQL ใด (ลบ, แทรก, อัพเดตหรือเลือก) ทำให้เกิดการล็อคนั้น?

ฉันรู้ว่าmost_recent_query_handleคอลัมน์ของsys.dm_exec_connectionsDMV ทำให้เรามีข้อความของการสืบค้นล่าสุดที่ดำเนินการ แต่หลายครั้งแบบสอบถามอื่น ๆ วิ่งมาก่อนภายใต้เซสชั่นเดียวกัน (SPID) และยังคงล็อค

ฉันใช้sp_whoisactiveขั้นตอน (จาก Adam Machanic) แล้วและจะแสดงเฉพาะแบบสอบถามที่อยู่ในบัฟเฟอร์อินพุตในขณะนี้ (คิดว่าDBCC INPUTBUFFER @spid) ซึ่งไม่เสมอไป (และในกรณีของฉันมักจะไม่เคย) คือแบบสอบถามที่ได้รับการล็อค

ตัวอย่างเช่น:

  1. เปิดธุรกรรม / เซสชัน
  2. ดำเนินการคำสั่ง (ที่ถือล็อคทรัพยากร)
  3. exec คำสั่งอื่นในเซสชั่นเดียวกัน
  4. เปิดธุรกรรม / เซสชันอื่นแล้วลองแก้ไขทรัพยากรที่ถูกล็อคในขั้นตอนที่ 2

sp_whoisactiveขั้นตอนจะชี้ให้เห็นคำสั่งในขั้นตอนที่ 3 ซึ่งไม่ได้เป็นผู้รับผิดชอบในการล็อคและจึงไม่เป็นประโยชน์

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

ฉันมีคำถามติดตาม: กรอบงานเพื่อระบุการบล็อกการสืบค้นได้อย่างมีประสิทธิภาพ

คำตอบ:


15

SQL Server ไม่เก็บประวัติของคำสั่งที่ได้รับการดำเนินการ1,2 คุณสามารถตรวจสอบสิ่งที่วัตถุมีล็อค แต่คุณไม่สามารถจำเป็นต้องเห็นสิ่งที่เกิดจากคำสั่งล็อคเหล่านั้น

ตัวอย่างเช่นหากคุณดำเนินการคำสั่งนี้:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

และดูข้อความ SQL ผ่านจุดจับ sql ล่าสุดคุณจะเห็นข้อความแจ้งว่าปรากฏขึ้น อย่างไรก็ตามหากเซสชันทำสิ่งนี้:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

คุณจะเห็นSELECT * FROM dbo.TestLock;คำสั่งเท่านั้นแม้ว่าธุรกรรมนั้นยังไม่ได้รับการยอมรับและINSERTคำสั่งนั้นกำลังบล็อกผู้อ่านจากdbo.TestLockตาราง

ฉันใช้สิ่งนี้เพื่อค้นหาธุรกรรมที่ไม่มีข้อผูกมัดที่กำลังบล็อกเซสชันอื่น:

/*
    This query shows sessions that are blocking other sessions, including sessions that are 
    not currently processing requests (for instance, they have an open, uncommitted transaction).

    By:  Max Vernon, 2017-03-20
*/
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; --reduce possible blocking by this query.

USE tempdb;

IF OBJECT_ID('tempdb..#dm_tran_session_transactions') IS NOT NULL
DROP TABLE #dm_tran_session_transactions;
SELECT *
INTO #dm_tran_session_transactions
FROM sys.dm_tran_session_transactions;

IF OBJECT_ID('tempdb..#dm_exec_connections') IS NOT NULL
DROP TABLE #dm_exec_connections;
SELECT *
INTO #dm_exec_connections
FROM sys.dm_exec_connections;

IF OBJECT_ID('tempdb..#dm_os_waiting_tasks') IS NOT NULL
DROP TABLE #dm_os_waiting_tasks;
SELECT *
INTO #dm_os_waiting_tasks
FROM sys.dm_os_waiting_tasks;

IF OBJECT_ID('tempdb..#dm_exec_sessions') IS NOT NULL
DROP TABLE #dm_exec_sessions;
SELECT *
INTO #dm_exec_sessions
FROM sys.dm_exec_sessions;

IF OBJECT_ID('tempdb..#dm_exec_requests') IS NOT NULL
DROP TABLE #dm_exec_requests;
SELECT *
INTO #dm_exec_requests
FROM sys.dm_exec_requests;

;WITH IsolationLevels AS 
(
    SELECT v.*
    FROM (VALUES 
              (0, 'Unspecified')
            , (1, 'Read Uncomitted')
            , (2, 'Read Committed')
            , (3, 'Repeatable')
            , (4, 'Serializable')
            , (5, 'Snapshot')
        ) v(Level, Description)
)
, trans AS 
(
    SELECT dtst.session_id
        , blocking_sesion_id = 0
        , Type = 'Transaction'
        , QueryText = dest.text
    FROM #dm_tran_session_transactions dtst 
        LEFT JOIN #dm_exec_connections dec ON dtst.session_id = dec.session_id
    OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
)
, tasks AS 
(
    SELECT dowt.session_id
        , dowt.blocking_session_id
        , Type = 'Waiting Task'
        , QueryText = dest.text
    FROM #dm_os_waiting_tasks dowt
        LEFT JOIN #dm_exec_connections dec ON dowt.session_id = dec.session_id
    OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
    WHERE dowt.blocking_session_id IS NOT NULL
)
, requests AS 
(
SELECT des.session_id
    , der.blocking_session_id
    , Type = 'Session Request'
    , QueryText = dest.text
FROM #dm_exec_sessions des
    INNER JOIN #dm_exec_requests der ON des.session_id = der.session_id
OUTER APPLY sys.dm_exec_sql_text(der.sql_handle) dest
WHERE der.blocking_session_id IS NOT NULL
    AND der.blocking_session_id > 0 
)
, Agg AS (
    SELECT SessionID = tr.session_id
        , ItemType = tr.Type
        , CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = tr.session_id)
        , BlockedBySessionID = tr.blocking_sesion_id
        , QueryText = tr.QueryText
    FROM trans tr
    WHERE EXISTS (
        SELECT 1
        FROM requests r
        WHERE r.blocking_session_id = tr.session_id
        )
    UNION ALL
    SELECT ta.session_id
        , ta.Type
        , CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = ta.session_id)
        , BlockedBySessionID = ta.blocking_session_id
        , ta.QueryText
    FROM tasks ta
    UNION ALL
    SELECT rq.session_id
        , rq.Type
        , CountOfBlockedSessions =  (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = rq.session_id)
        , BlockedBySessionID = rq.blocking_session_id
        , rq.QueryText
    FROM requests rq
)
SELECT agg.SessionID
    , ItemType = STUFF((SELECT ', ' + COALESCE(a.ItemType, '') FROM agg a WHERE a.SessionID = agg.SessionID ORDER BY a.ItemType FOR XML PATH ('')), 1, 2, '')
    , agg.BlockedBySessionID
    , agg.QueryText
    , agg.CountOfBlockedSessions
    , des.host_name
    , des.login_name
    , des.is_user_process
    , des.program_name
    , des.status
    , TransactionIsolationLevel = il.Description
FROM agg 
    LEFT JOIN #dm_exec_sessions des ON agg.SessionID = des.session_id
    LEFT JOIN IsolationLevels il ON des.transaction_isolation_level = il.Level
GROUP BY agg.SessionID
    , agg.BlockedBySessionID
    , agg.CountOfBlockedSessions
    , agg.QueryText
    , des.host_name
    , des.login_name
    , des.is_user_process
    , des.program_name
    , des.status
    , il.Description
ORDER BY 
    agg.BlockedBySessionID
    , agg.CountOfBlockedSessions
    , agg.SessionID;

หากเราตั้งค่า test-bed แบบง่าย ๆ ใน SSMS ด้วยหน้าต่างการสืบค้นสองชุดเราจะเห็นว่าเราสามารถเห็นคำสั่งที่ใช้งานล่าสุดเท่านั้น

ในหน้าต่างแบบสอบถามแรกให้เรียกใช้สิ่งนี้:

CREATE TABLE dbo.TestLock
(
    id int NOT NULL IDENTITY(1,1)
);
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

ในหน้าต่างที่สองให้เรียกใช้สิ่งนี้:

SELECT *
FROM  dbo.TestLock

ตอนนี้ถ้าเราเรียกใช้แบบสอบถามการปิดกั้นการทำธุรกรรมจากด้านบนเราจะเห็นผลลัพธ์ต่อไปนี้:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ประเภทรายการ║ถูกบล็อคโดย BessSessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ธุรกรรม║ 0 ║การทำธุรกรรมเริ่มต้น║
║║║║ INSERT INTO dbo.TestLock ค่าเริ่มต้น║
║ 68 ║คำขอเซสชันกำลังรองาน║ 67 ║เลือก * ║
║║║║จาก dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(ฉันได้ลบคอลัมน์ที่ไม่เกี่ยวข้องออกจากส่วนท้ายของผลลัพธ์)

ตอนนี้ถ้าเราเปลี่ยนหน้าต่างคิวรีแรกเป็น:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

และเรียกใช้หน้าต่างแบบสอบถามที่ 2 อีกครั้ง:

SELECT *
FROM  dbo.TestLock

เราจะเห็นผลลัพธ์นี้จากข้อความค้นหาการบล็อกธุรกรรม:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ประเภทรายการ║ถูกบล็อคโดย BessSessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
║ 67 ║ธุรกรรม║ 0 ║ SELECT * ║
ROM ║║║จาก dbo.TestLock; ║
║ 68 ║คำขอเซสชันกำลังรองาน║ 67 ║เลือก * ║
║║║║จาก dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩════════════════════╝

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

แบบสอบถามด้านล่างแสดงแผนแบบสอบถามสำหรับแบบสอบถามทดสอบด้านบนเนื่องจากโพรซีเดอร์แคชของฉันไม่ยุ่งมาก

SELECT TOP(30) t.text
    , p.query_plan
    , deqs.execution_count
    , deqs.total_elapsed_time
    , deqs.total_logical_reads
    , deqs.total_logical_writes
    , deqs.total_logical_writes
    , deqs.total_rows
    , deqs.total_worker_time
    , deqs.*
FROM sys.dm_exec_query_stats deqs
OUTER APPLY sys.dm_exec_sql_text(deqs.sql_handle) t 
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle) p
WHERE t.text LIKE '%dbo.TestLock%'  --change this to suit your needs
    AND t.text NOT LIKE '/\/\/\/\/EXCLUDE ME/\/\/\/\/\'
ORDER BY 
    deqs.total_worker_time DESC;

ผลลัพธ์ของแบบสอบถามนี้อาจช่วยให้คุณค้นหาผู้กระทำผิด แต่ระวังการตรวจสอบขั้นตอนการแคชเช่นนี้สามารถเรียกร้องมากในระบบไม่ว่าง

2 SQL Server 2016 และสูงกว่าเสนอQuery Storeซึ่งจะเก็บประวัติของการสืบค้นทั้งหมด


ขอบคุณ @ Max อธิบายได้ดีมาก ข้อสงสัยนี้เกิดขึ้นขณะทำการวิเคราะห์Blocked Process Reportsคุณสมบัติเพื่อค้นหาสาเหตุของการบล็อกสถานการณ์ในการผลิต แต่ละธุรกรรมรันหลายเคียวรีและส่วนใหญ่ครั้งล่าสุด (ที่แสดงในอินพุตบัฟเฟอร์ที่ BPR) ไม่ค่อยถือล็อค ดูเหมือนว่าทรัพยากรสุดท้ายของฉันในการแก้ไขปัญหานี้คือการตั้งค่าเซสชัน xEvents ที่มีน้ำหนักเบาเพื่อบอกฉันว่าแบบสอบถามใดทำงานภายใต้แต่ละเซสชัน หากคุณรู้ว่าบทความแสดงตัวอย่างของเรื่องนี้ฉันจะขอบคุณ
tanitelle

เกี่ยวกับ Query Store มันมีประโยชน์มาก แต่ขาดข้อมูล SPID ขอบคุณอยู่ดี
tanitelle

ค่อนข้างซ้ำกับdba.stackexchange.com/questions/187794/…
Mitch Wheat เมื่อ

6

เพื่อเติมเต็มคำตอบของ Maxฉันพบด้านล่างอรรถประโยชน์ที่มีประโยชน์อย่างยิ่ง:

ฉันใช้ beta_lockinfo เมื่อฉันต้องการดำน้ำลึกเพื่อปิดกั้นและวิเคราะห์สิ่งที่และวิธีการปิดกั้นเกิดขึ้น - ซึ่งมีประโยชน์มาก

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


1
ว้าวว่า Erland Sommarskog proc นั้นน่าทึ่งมาก
Max Vernon

1
ใช่ .. ฉันใช้มันเมื่อฉันต้องดำน้ำลึกเพื่อปิดกั้นรายละเอียด
Kin Shah
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.