Bagaimana menemukan kueri yang masih memegang kunci?

15

Meminta sys.dm_tran_locksDMV menunjukkan kepada kita sesi (SPID) mana yang menahan kunci pada sumber daya seperti tabel, halaman, dan baris.

Untuk setiap kunci yang diperoleh, apakah ada cara untuk menentukan pernyataan SQL mana (hapus, masukkan, perbarui atau pilih) yang menyebabkan kunci itu?

Saya tahu bahwa most_recent_query_handlekolom sys.dm_exec_connectionsDMV memberi kami teks dari kueri terakhir yang dieksekusi, tetapi beberapa kali kueri lain berjalan sebelum di bawah sesi yang sama (SPID) dan masih memegang kunci.

Saya sudah menggunakan sp_whoisactiveprosedur (dari Adam Machanic) dan itu hanya menunjukkan permintaan yang ada di buffer input saat ini (berpikir DBCC INPUTBUFFER @spid), yang tidak selalu (dan dalam kasus saya biasanya tidak pernah) adalah permintaan yang memperoleh kunci.

Sebagai contoh:

  1. buka transaksi / sesi
  2. exec pernyataan (yang memegang kunci pada sumber daya)
  3. mengeksekusi pernyataan lain pada sesi yang sama
  4. buka transaksi / sesi lain dan cobalah untuk memodifikasi sumber daya yang dikunci pada langkah 2.

The sp_whoisactiveprosedur akan menunjukkan pernyataan pada langkah 3, yang tidak bertanggung jawab untuk kunci, dan dengan demikian tidak berguna.

Pertanyaan ini berasal dari melakukan analisis menggunakan fitur Laporan Proses yang Diblokir , untuk menemukan akar penyebab dari memblokir skenario dalam produksi. Setiap transaksi menjalankan beberapa permintaan, dan sebagian besar waktu yang terakhir (yang ditunjukkan pada input buffer di BPR) jarang yang memegang kunci.

Saya punya pertanyaan tindak lanjut: Kerangka kerja untuk secara efektif mengidentifikasi kueri pemblokiran

tanitelle
sumber

Jawaban:

15

SQL Server tidak menyimpan riwayat perintah yang telah dieksekusi 1,2 . Anda dapat menentukan apa benda memiliki kunci, tetapi Anda tidak dapat selalu melihat pernyataan apa yang menyebabkan orang-orang kunci.

Misalnya, jika Anda menjalankan pernyataan ini:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

Dan lihat Teks SQL melalui menangani sql terbaru, Anda akan melihat pernyataan itu muncul. Namun, jika sesi melakukan ini:

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

Anda hanya akan melihat SELECT * FROM dbo.TestLock;pernyataan itu, meskipun transaksi belum dilakukan, dan INSERTpernyataan itu menghalangi pembaca dbo.TestLock.

Saya menggunakan ini untuk mencari transaksi yang tidak dikomit yang memblokir sesi lain:

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

Jika kita menyiapkan test-bed sederhana di SSMS dengan beberapa jendela permintaan, kita dapat melihat bahwa kita hanya bisa melihat pernyataan yang paling aktif saat ini.

Di jendela kueri pertama, jalankan ini:

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

Di jendela kedua, jalankan ini:

SELECT *
FROM  dbo.TestLock

Sekarang, jika kita menjalankan kueri transaksi pemblokiran yang tidak dikomit dari atas, kita melihat output berikut:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ Transaksi ║ 0 ║ TRANSAKSI BEGIN ║
║ ║ ║ ║ INSERT KE dBo.TestLock NILAI DEFAULT
║ 68 ║ Permintaan Sesi, Tugas Tunggu ║ 67 ║ PILIH * *
║ ║ ║ ║ DARI dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(Saya telah menghapus beberapa kolom yang tidak relevan dari akhir hasil).

Sekarang, jika kita mengubah jendela permintaan pertama menjadi ini:

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

dan jalankan kembali jendela permintaan ke-2:

SELECT *
FROM  dbo.TestLock

Kami akan melihat output ini dari kueri transaksi pemblokiran:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
║ 67 ║ Transaksi ║ 0 ║ PILIH * ║
║ ║ ║ ║ DARI dbo.TestLock; ║
║ 68 ║ Permintaan Sesi, Tugas Tunggu ║ 67 ║ PILIH * *
║ ║ ║ ║ DARI dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩════════════════════╝

1 - tidak sepenuhnya benar. Ada cache prosedur, yang mungkin berisi pernyataan yang bertanggung jawab atas kunci. Namun, mungkin tidak mudah untuk menentukan pernyataan mana yang merupakan penyebab kunci karena mungkin ada banyak pertanyaan dalam cache yang menyentuh sumber daya yang dimaksud.

Kueri di bawah ini menunjukkan rencana kueri untuk kueri pengujian di atas karena cache prosedur saya tidak terlalu sibuk.

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;

Hasil dari query ini dapat membantu anda untuk menemukan pelakunya, tetapi menyadari, memeriksa cache prosedur seperti ini bisa sangat menuntut pada sistem sibuk.

2 SQL Server 2016 dan di atasnya menawarkan Query Store , yang tidak menyimpan riwayat kueri lengkap yang dijalankan.

Max Vernon
sumber
Terima kasih @ Max, dijelaskan dengan sangat baik. Keraguan ini lahir saat melakukan analisis Blocked Process Reportsfitur, untuk menemukan akar penyebab memblokir skenario dalam produksi. Setiap transaksi menjalankan beberapa permintaan, dan sebagian besar waktu yang terakhir (yang ditunjukkan pada input buffer di BPR) jarang yang memegang kunci. Tampaknya sumber daya terakhir saya untuk menyelesaikan ini adalah dengan mengatur sesi xEvents yang ringan untuk memberi tahu saya pertanyaan apa yang berjalan di bawah setiap sesi. Jika Anda tahu artikel yang menunjukkan contoh ini, saya akan berterima kasih.
tanitelle
Juga mengenai Query Store, ini sangat berguna, tetapi tidak memiliki informasi SPID. Bagaimanapun, terima kasih.
tanitelle
cukup banyak duplikat dari dba.stackexchange.com/questions/187794/…
Mitch Wheat
6

Untuk melengkapi jawaban Max , saya menemukan utilitas di bawah ini sangat berguna:

Saya menggunakan beta_lockinfo ketika saya ingin menyelam lebih dalam ke pemblokiran dan menganalisis apa dan bagaimana pemblokiran muncul - yang sangat berguna.

beta_lockinfo adalah prosedur tersimpan yang memberikan informasi tentang proses dan kunci yang mereka pegang serta transaksi aktifnya. beta_lockinfo dirancang untuk mengumpulkan sebanyak mungkin informasi tentang situasi pemblokiran, sehingga Anda dapat langsung menemukan pelakunya dan membunuh proses pemblokiran jika situasinya putus asa. Kemudian Anda dapat duduk dan menganalisis output dari beta_lockinfo untuk memahami bagaimana situasi pemblokiran muncul dan mencari tahu tindakan apa yang harus diambil untuk mencegah situasi dari terulang kembali. Output dari beta_lockinfo menunjukkan semua proses aktif serta proses pasif dengan kunci, objek mana yang dikunci, perintah apa yang terakhir mereka kirimkan dan pernyataan apa yang mereka jalankan. Anda juga mendapatkan paket permintaan untuk laporan saat ini.

Kin Shah
sumber
1
wow, proc Erland Sommarskog itu luar biasa.
Max Vernon
1
Yeh .. saya menggunakannya ketika saya harus menyelam jauh ke detail pemblokiran.
Kin Shah