Làm thế nào để tìm truy vấn vẫn đang giữ một khóa?


15

Truy vấn sys.dm_tran_locksDMV cho chúng ta thấy phiên nào (SPID) đang giữ khóa trên các tài nguyên như bảng, trang và hàng.

Đối với mỗi khóa có được, có cách nào để xác định câu lệnh SQL nào (xóa, chèn, cập nhật hoặc chọn) gây ra khóa đó không?

Tôi biết rằng most_recent_query_handlecột của sys.dm_exec_connectionsDMV cung cấp cho chúng tôi văn bản của truy vấn cuối cùng được thực hiện, nhưng nhiều lần các truy vấn khác đã chạy trước đó trong cùng một phiên (SPID) và vẫn đang giữ các khóa.

Tôi đã sử dụng sp_whoisactivethủ tục (từ Adam Machanic) và nó chỉ hiển thị truy vấn trên bộ đệm đầu vào tại thời điểm này DBCC INPUTBUFFER @spid, mà không phải lúc nào (và trong trường hợp của tôi thường không bao giờ) là truy vấn có được khóa.

Ví dụ:

  1. mở giao dịch / phiên
  2. thực hiện một câu lệnh (giữ một khóa trên một tài nguyên)
  3. thực hiện một tuyên bố khác trong cùng một phiên
  4. mở một giao dịch / phiên khác và cố gắng sửa đổi tài nguyên bị khóa ở bước 2.

Quy sp_whoisactivetrình sẽ chỉ ra tuyên bố ở bước 3, không chịu trách nhiệm về khóa và do đó không hữu ích.

Câu hỏi này xuất phát từ việc thực hiện phân tích bằng tính năng Báo cáo quy trình bị chặn , để tìm ra nguyên nhân gốc của các kịch bản chặn trong sản xuất. Mỗi giao dịch chạy một số truy vấn và hầu hết thời gian của truy vấn cuối cùng (được hiển thị trên bộ đệm đầu vào tại BPR) hiếm khi là một truy vấn giữ khóa.

Tôi có một câu hỏi tiếp theo: Khung để xác định hiệu quả các truy vấn chặn

Câu trả lời:


15

SQL Server không lưu giữ lịch sử của các lệnh đã được thực thi 1,2 . Bạn có thể xác định đối tượng nào có khóa, nhưng bạn không nhất thiết phải xem câu lệnh nào gây ra các khóa đó.

Ví dụ: nếu bạn thực hiện câu lệnh này:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

Và nhìn vào Văn bản SQL thông qua trình xử lý sql gần đây nhất, bạn sẽ thấy câu lệnh đó xuất hiện. Tuy nhiên, nếu phiên làm việc này:

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

Bạn chỉ nhìn thấy SELECT * FROM dbo.TestLock;tuyên bố, mặc dù giao dịch chưa được thực hiện và INSERTtuyên bố đang chặn độc giả đối với dbo.TestLockbảng.

Tôi sử dụng điều này để tìm kiếm các giao dịch không được cam kết đang chặn các phiên khác:

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

Nếu chúng ta thiết lập một giường thử nghiệm đơn giản trong SSMS với một vài cửa sổ truy vấn, chúng ta có thể thấy rằng chúng ta chỉ có thể thấy câu lệnh hoạt động gần đây nhất.

Trong cửa sổ truy vấn đầu tiên, hãy chạy này:

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

Trong cửa sổ thứ hai, chạy này:

SELECT *
FROM  dbo.TestLock

Bây giờ, nếu chúng tôi chạy truy vấn chặn giao dịch không được cam kết từ phía trên, chúng tôi sẽ thấy đầu ra sau:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ ItemType BlockedBySessionID ║ QueryText
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
67 ║ Giao dịch 0 ║ GIAO DỊCH BEGIN
║ ║ XÁC NHẬN VÀO dbo.TestLock DEFAULT GIÁ TRỊ
68 Yêu cầu phiên, Nhiệm vụ chờ 67 ║ CHỌN *
║ ║ TỪ dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(Tôi đã xóa một số cột không liên quan từ cuối kết quả).

Bây giờ, nếu chúng ta thay đổi cửa sổ truy vấn đầu tiên thành này:

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

và chạy lại cửa sổ truy vấn thứ 2:

SELECT *
FROM  dbo.TestLock

Chúng ta sẽ thấy đầu ra này từ truy vấn chặn giao dịch:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ ItemType BlockedBySessionID ║ QueryText
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
67 ║ Giao dịch ║ 0 ║ CHỌN *
║ ║ ║ TỪ dbo.TestLock; ║
68 Yêu cầu phiên, Nhiệm vụ chờ 67 ║ CHỌN *
║ ║ TỪ dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩════════════════════╝

1 - không hoàn toàn đúng. Có bộ đệm thủ tục, có thể chứa câu lệnh chịu trách nhiệm cho khóa. Tuy nhiên, có thể không dễ để xác định câu lệnh nào là nguyên nhân thực sự của khóa vì có thể có nhiều truy vấn trong bộ đệm chạm vào tài nguyên được đề cập.

Truy vấn bên dưới hiển thị kế hoạch truy vấn cho các truy vấn thử nghiệm ở trên vì bộ đệm thủ tục của tôi không quá bận rộn.

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;

Kết quả của truy vấn này có thể cho phép bạn tìm ra thủ phạm, nhưng lưu ý, việc kiểm tra bộ đệm thủ tục như thế này có thể khá đòi hỏi trên một hệ thống bận rộn.

2 SQL Server 2016 và phục vụ trên các cửa hàng Query , mà không giữ lại lịch sử hoàn chỉnh các truy vấn thực hiện.


Cảm ơn @Max, giải thích rất tốt. Nghi ngờ này được sinh ra trong khi thực hiện phân tích Blocked Process Reportstính năng, để tìm ra nguyên nhân gốc rễ của các kịch bản chặn trong sản xuất. Mỗi giao dịch chạy một số truy vấn và hầu hết thời gian của truy vấn cuối cùng (được hiển thị trên bộ đệm đầu vào tại BPR) hiếm khi là một truy vấn giữ khóa. Dường như tài nguyên cuối cùng của tôi để giải quyết vấn đề này là đặt phiên xEvents nhẹ để cho tôi biết những truy vấn nào được chạy trong mỗi phiên. Nếu bạn biết một bài viết cho thấy một ví dụ về điều này, tôi sẽ biết ơn.
tanitelle

Cũng liên quan đến Cửa hàng truy vấn, nó rất hữu ích, nhưng thiếu thông tin SPID. Dẫu sao cũng xin cảm ơn.
tanitelle


6

Để bổ sung cho câu trả lời của Max , tôi đã tìm thấy các tiện ích dưới đây cực kỳ hữu ích:

Tôi sử dụng beta_lockinfo khi tôi muốn đi sâu vào việc chặn và phân tích những gì và cách chặn phát sinh - điều này cực kỳ hữu ích.

beta_lockinfo là một thủ tục được lưu trữ cung cấp thông tin về các quy trình và các khóa họ giữ cũng như các giao dịch đang hoạt động của họ. beta_lockinfo được thiết kế để thu thập càng nhiều thông tin về tình huống chặn càng tốt, để bạn có thể ngay lập tức tìm ra thủ phạm và tiêu diệt quá trình chặn nếu tình huống tuyệt vọng. Sau đó, bạn có thể ngồi lại và phân tích đầu ra từ beta_lockinfo để hiểu tình huống chặn phát sinh như thế nào và tìm ra những hành động cần thực hiện để ngăn chặn tình huống tái diễn. Đầu ra từ beta_lockinfo cho thấy tất cả quy trình hoạt động cũng như các quy trình thụ động có khóa, đối tượng nào họ khóa, lệnh nào họ gửi lần cuối và câu lệnh nào họ đang thực hiện. Bạn cũng có được các kế hoạch truy vấn cho các báo cáo hiện tại.


1
wow, Erland Sommarskog Proc thật tuyệt vời.
Max Vernon

1
Yeh .. tôi sử dụng nó khi tôi phải đi sâu vào việc chặn các chi tiết.
Kin Shah
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.