Apa penyebab utama kebuntuan dan dapatkah mereka dicegah?

55

Baru-baru ini salah satu aplikasi ASP.NET kami menampilkan kesalahan kebuntuan basis data dan saya diminta untuk memeriksa dan memperbaiki kesalahan tersebut. Saya berhasil menemukan penyebab kebuntuan adalah prosedur tersimpan yang secara ketat memperbarui tabel dalam kursor.

Ini adalah pertama kalinya saya melihat kesalahan ini dan tidak tahu cara melacak dan memperbaikinya secara efektif. Saya mencoba semua cara yang mungkin saya tahu, dan akhirnya menemukan bahwa tabel yang sedang diperbarui tidak memiliki kunci utama! untungnya itu adalah kolom identitas.

Saya kemudian menemukan pengembang yang membuat database untuk penyebaran kacau. Saya menambahkan kunci utama dan masalah terpecahkan.

Saya merasa bahagia dan kembali ke proyek saya, dan melakukan riset untuk menemukan alasan kebuntuan itu ...

Rupanya, itu adalah kondisi menunggu melingkar yang menyebabkan kebuntuan. Pembaruan tampaknya membutuhkan waktu lebih lama tanpa kunci primer daripada dengan kunci primer.

Saya tahu itu bukan kesimpulan yang jelas, itu sebabnya saya memposting di sini ...

  • Apakah kunci utama yang hilang adalah masalahnya?
  • Apakah ada kondisi lain yang menyebabkan kebuntuan selain (saling pengecualian, tahan dan tunggu, tidak ada preemption dan menunggu bundar)?
  • Bagaimana saya mencegah dan melacak kebuntuan?
CoderHawk
sumber
2
Mayoritas IME (semua?) Dari kebuntuan yang saya lihat terjadi karena menunggu melingkar (terutama karena penggunaan pemicu yang terlalu bersemangat).
Sathyajith Bhat
Circularity adalah salah satu syarat kebuntuan. Anda dapat menghindari kebuntuan apa pun jika semua sesi Anda mendapatkan kunci dalam urutan yang sama.
Peter G.

Jawaban:

38

melacak kebuntuan adalah yang lebih mudah dari keduanya:

Secara default, deadlock tidak ditulis dalam log kesalahan. Anda dapat menyebabkan SQL untuk menulis kebuntuan ke log kesalahan dengan bendera jejak 1204 dan 3605.

Menulis info kebuntuan ke log galat SQL Server: DBCC TRACEON (-1, 1204, 3605)

Matikan: DBCC TRACEOFF (-1, 1204, 3605)

Lihat "Pemecahan Masalah Deadlocks" untuk diskusi tentang jejak flag 1204 dan output yang akan Anda dapatkan ketika dihidupkan. https://msdn.microsoft.com/en-us/library/ms178104.aspx

Pencegahan lebih sulit, pada dasarnya Anda harus memperhatikan hal-hal berikut:

Kode Blok 1 mengunci sumber A, lalu sumber B, dalam urutan itu.

Kode Blok 2 mengunci sumber B, lalu sumber A, dalam urutan itu.

Ini adalah kondisi klasik di mana kebuntuan dapat terjadi, jika penguncian kedua sumber daya tersebut tidak bersifat atomik, Blok Kode 1 dapat mengunci A dan dikosongkan, lalu Blok Kode 2 mengunci B sebelum A mendapatkan waktu pemrosesan kembali. Sekarang Anda memiliki jalan buntu.

Untuk mencegah kondisi ini, Anda dapat melakukan sesuatu seperti berikut ini

Kode Blok A (kode psuedo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Kode Blok B (kode semu)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

tidak lupa untuk membuka kunci A dan B ketika selesai dengan mereka

ini akan mencegah kebuntuan antara blok kode A dan blok kode B

Dari perspektif basis data, saya tidak yakin bagaimana cara mencegah situasi ini, karena kunci ditangani oleh basis data itu sendiri, yaitu baris / tabel mengunci saat memperbarui data. Di mana saya telah melihat sebagian besar masalah terjadi adalah di mana Anda melihat masalah Anda, di dalam kursor. Kursor sangat tidak efisien, hindari jika memungkinkan.

Es hitam
sumber
Apakah Anda bermaksud mengunci sumber A sebelum sumber B di Kode Blok B? Seperti yang tertulis, ini akan menyebabkan kebuntuan .. seperti yang Anda sebutkan di komentar sebelumnya. Sebisa mungkin, Anda ingin selalu mengunci sumber daya dalam urutan yang sama, bahkan jika Anda memerlukan kueri dummy di awal untuk memastikan urutan penguncian.
Gerard ONeill
23

artikel favorit saya untuk membaca dan belajar tentang kebuntuan adalah: Bicara Sederhana - Lacak kebuntuan dan SQL Server Central - Menggunakan Profiler untuk menyelesaikan kebuntuan . Mereka akan memberi Anda sampel dan saran tentang cara menangani situasi yang sulit.

Singkatnya, untuk menyelesaikan masalah saat ini, saya akan membuat transaksi menjadi lebih pendek, mengambil bagian yang tidak dibutuhkan dari mereka, mengurus urutan penggunaan objek, melihat tingkat isolasi apa yang sebenarnya diperlukan, tidak membaca yang tidak dibutuhkan data...

Tetapi lebih baik membaca artikel, mereka akan jauh lebih baik dalam saran.

Marian
sumber
16

Kadang-kadang jalan buntu dapat diselesaikan dengan menambahkan pengindeksan, karena memungkinkan database untuk mengunci catatan individual daripada seluruh tabel, sehingga Anda mengurangi pertengkaran dan kemungkinan hal-hal menjadi macet.

Misalnya, dalam InnoDB :

Jika Anda tidak memiliki indeks yang cocok untuk pernyataan Anda dan MySQL harus memindai seluruh tabel untuk memproses pernyataan, setiap baris tabel menjadi terkunci, yang pada gilirannya memblokir semua sisipan oleh pengguna lain ke tabel. Penting untuk membuat indeks yang baik sehingga permintaan Anda tidak perlu memindai banyak baris.

Solusi umum lainnya adalah mematikan konsistensi transaksional ketika tidak diperlukan, atau mengubah tingkat isolasi Anda , misalnya, pekerjaan jangka panjang untuk menghitung statistik ... jawaban yang dekat umumnya cukup, Anda tidak perlu angka yang tepat, karena mereka berubah dari bawah Anda. Dan jika perlu 30 menit untuk menyelesaikannya, Anda tidak ingin itu menghentikan semua transaksi lain pada tabel tersebut.

...

Adapun untuk melacak mereka, itu tergantung pada perangkat lunak database yang Anda gunakan.

Joe
sumber
Merupakan kesopanan yang umum untuk memberikan komentar saat melakukan downvoting ... Ini adalah jawaban yang valid, pernyataan pilihan ditingkatkan ke kunci meja dan mengambil selamanya pasti dapat menyebabkan kebuntuan.
BlackICE
1
MS SQLServer juga dapat memberikan perilaku penguncian tak terduga jika indeks tidak berkerumun. Ini akan diam-diam mengabaikan arah Anda untuk menggunakan penguncian tingkat baris dan akan melakukan penguncian tingkat halaman. Anda kemudian bisa mendapatkan kebuntuan menunggu di halaman.
Jay
7

Hanya untuk mengembangkan pada hal kursor. ini memang sangat buruk. Itu mengunci seluruh tabel kemudian memproses baris satu per satu.

Yang terbaik adalah melalui baris dengan cara kursor menggunakan loop sementara

Dalam loop sementara, pilih akan dilakukan untuk setiap baris dalam loop dan kunci akan terjadi hanya pada satu baris pada saat itu. Sisa data dalam tabel gratis untuk kueri, sehingga mengurangi kemungkinan kebuntuan terjadi.

Plus lebih cepat. Membuat Anda bertanya-tanya mengapa ada kursor.

Berikut ini contoh struktur semacam ini:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Jika bidang ID Anda jarang, Anda mungkin ingin menarik daftar ID terpisah dan beralih melalui itu:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END
Nicolas de Fontenay
sumber
6

Kehilangan kunci primer bukan masalahnya. Setidaknya dengan sendirinya. Pertama, Anda tidak perlu primer untuk memiliki indeks. Kedua, bahkan jika Anda melakukan pemindaian tabel (yang harus terjadi jika permintaan khusus Anda tidak menggunakan indeks, kunci tabel tidak akan dengan sendirinya menyebabkan kebuntuan. Proses penulisan akan menunggu untuk dibaca, dan proses membaca akan menunggu tulisan, dan tentu saja membaca tidak harus menunggu satu sama lain sama sekali.

Menambah jawaban lain, Tingkat isolasi transaksi penting, karena pembacaan berulang dan serial adalah penyebab kunci 'baca' ditahan sampai akhir transaksi. Mengunci sumber daya tidak menyebabkan jalan buntu. Tetap terkunci tidak. Operasi tulis selalu menjaga sumber dayanya terkunci sampai akhir transaksi.

Strategi pencegahan kunci favorit saya adalah menggunakan fitur 'snapshot'. Fitur Baca Mengkomit Snapshot berarti membaca tidak menggunakan kunci! Dan jika Anda memerlukan lebih banyak kontrol daripada 'Komitmen baca', ada fitur 'Level isolasi pengambilan gambar'. Yang ini memungkinkan transaksi serial (menggunakan istilah MS di sini) terjadi tanpa memblokir pemain lain.

Terakhir, satu kelas deadlock dapat dicegah dengan menggunakan kunci pembaruan. Jika Anda membaca dan menahan read (HOLD, atau menggunakan Repeatable Read), dan proses lain melakukan hal yang sama, maka keduanya mencoba memperbarui catatan yang sama, Anda akan menemui jalan buntu. Tetapi jika keduanya meminta kunci pembaruan, proses kedua akan menunggu yang pertama, sementara memungkinkan proses lain untuk membaca data menggunakan kunci bersama sampai data benar-benar ditulis. Ini tentu saja tidak akan berfungsi jika salah satu proses masih meminta kunci HOLD bersama.

Gerard ONeill
sumber
-2

Sementara kursor lambat dalam SQL Server, Anda bisa menghindari kebuntuan dalam kursor dengan menarik data sumber untuk kursor ke dalam tabel Temp dan menjalankan kursor di atasnya. Ini menjaga kursor dari mengunci tabel data aktual dan satu-satunya kunci yang Anda dapatkan adalah untuk pembaruan atau sisipan yang dilakukan di dalam kursor yang hanya ditahan selama durasi penyisipan / pembaruan dan bukan untuk durasi kursor.

Bill Joyce
sumber