Bagaimana cara menangkap SqlException yang disebabkan oleh kebuntuan?

94

Dari aplikasi .NET 3.5 / C #, saya ingin menangkapnya SqlExceptiontetapi hanya jika hal itu disebabkan oleh kebuntuan pada contoh SQL Server 2008.

Pesan kesalahan tipikal adalah Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Namun, tampaknya itu bukan kode kesalahan yang terdokumentasi untuk pengecualian ini.

Memfilter pengecualian terhadap keberadaan kata kunci kebuntuan dalam pesan mereka tampaknya merupakan cara yang sangat buruk untuk mencapai perilaku ini. Apakah seseorang tahu cara yang benar untuk melakukan ini?

Joannes Vermorel
sumber
3
Saya (akhirnya) menemukan dokumentasi untuk kode kesalahan: msdn.microsoft.com/en-us/library/aa337376.aspx . Anda juga dapat menemukannya melalui SQL Server itu sendiri:select * from master.dbo.sysmessages where error=1205
Martin McNulty

Jawaban:

157

Kode kesalahan khusus Microsft SQL Server untuk kebuntuan adalah 1205 jadi Anda harus menangani SqlException dan memeriksanya. Jadi, misalnya jika untuk semua jenis SqlException Anda ingin gelembung pengecualiannya:

catch (SqlException ex)
{
    if (ex.Number == 1205)
    {
        // Deadlock 
    }
    else
        throw;
}

Atau, menggunakan pemfilteran pengecualian yang tersedia di C # 6

catch (SqlException ex) when (ex.Number == 1205)
{
    // Deadlock 
}

Hal praktis yang harus dilakukan untuk menemukan kode kesalahan SQL yang sebenarnya untuk pesan tertentu, adalah dengan mencari di sys.messages di SQL Server.

misalnya

SELECT * FROM sys.messages WHERE text LIKE '%deadlock%' AND language_id=1033

Cara alternatif untuk menangani kebuntuan (dari SQL Server 2005 dan yang lebih baru), adalah melakukannya dalam prosedur yang tersimpan menggunakan dukungan COBA ... CATCH:

BEGIN TRY
    -- some sql statements
END TRY
BEGIN CATCH
    IF (ERROR_NUMBER() = 1205)
        -- is a deadlock
    ELSE
        -- is not a deadlock
END CATCH

Ada contoh lengkap di sini di MSDN tentang bagaimana menerapkan logika coba lagi kebuntuan murni dalam SQL.

AdaTheDev
sumber
2
Perhatikan kode kesalahan adalah khusus vendor, jadi 1205 adalah kebuntuan untuk SQL Server, tetapi mungkin berbeda untuk Oracle, MySQL, dll.
Brianmearns
3
Bergantung pada lapisan datanya, SqlExceptionmungkin digabungkan satu sama lain. Jadi kita mungkin perlu menangkap jenis pengecualian apa pun dan memeriksanya, jika mereka bukan pengecualian kebuntuan secara langsung, periksa secara rekursif InnerException.
Frédéric
46

Karena saya kira Anda mungkin ingin mendeteksi kebuntuan, untuk dapat mencoba kembali operasi yang gagal, saya ingin memperingatkan Anda untuk sedikit gotcha. Saya harap Anda memaafkan saya karena sedikit keluar dari topik di sini.

Kebuntuan yang terdeteksi oleh basis data akan secara efektif mengembalikan transaksi yang Anda jalankan (jika ada), sementara koneksi tetap terbuka di .NET. Mencoba kembali operasi tersebut (dalam koneksi yang sama), berarti akan dijalankan dalam konteks tanpa transaksi dan ini dapat menyebabkan kerusakan data.

Penting untuk menyadari hal ini. Sebaiknya pertimbangkan koneksi lengkap ditakdirkan jika terjadi kegagalan yang disebabkan oleh SQL. Mencoba kembali operasi hanya dapat dilakukan pada level di mana transaksi ditentukan (dengan membuat ulang transaksi itu dan koneksinya).

Jadi ketika Anda mencoba kembali operasi yang gagal, pastikan Anda membuka koneksi yang benar-benar baru dan memulai transaksi baru.

Steven
sumber
4
Mengapa Anda membutuhkan koneksi yang benar-benar baru? Saya telah memposting pertanyaan tentang jawaban ini di sini .
Sam
3

Berikut adalah cara C # 6 untuk mendeteksi kebuntuan.

try
{
    //todo: Execute SQL. 
    //IMPORTANT, if you used Connection.BeginTransaction(), this try..catch must surround that code. You must rollback the original transaction, then recreate it and re-run all the code.
}
catch (SqlException ex) when (ex.Number == 1205)
{
    //todo: Retry SQL
}

Pastikan try..catch ini mengelilingi seluruh transaksi Anda. Menurut @Steven (lihat jawabannya untuk detailnya), ketika perintah sql gagal karena kebuntuan, itu menyebabkan transaksi digulung kembali dan, jika Anda tidak membuat ulang transaksi, percobaan ulang Anda akan dieksekusi di luar konteks transaksi dan dapat mengakibatkan inkonsistensi data.

Brian
sumber