Saya memiliki prosedur berikut (SQL Server 2008 R2):
create procedure usp_SaveCompanyUserData
@companyId bigint,
@userId bigint,
@dataTable tt_CoUserdata readonly
as
begin
set nocount, xact_abort on;
merge CompanyUser with (holdlock) as r
using (
select
@companyId as CompanyId,
@userId as UserId,
MyKey,
MyValue
from @dataTable) as newData
on r.CompanyId = newData.CompanyId
and r.UserId = newData.UserId
and r.MyKey = newData.MyKey
when not matched then
insert (CompanyId, UserId, MyKey, MyValue) values
(@companyId, @userId, newData.MyKey, newData.MyValue);
end;
CompanyId, UserId, MyKey membentuk kunci komposit untuk tabel target. CompanyId adalah kunci asing ke tabel induk. Juga, ada indeks non-clustered di CompanyId asc, UserId asc
.
Ini dipanggil dari berbagai utas, dan saya secara konsisten mendapatkan kebuntuan antara berbagai proses yang menyebut pernyataan yang sama. Pemahaman saya adalah bahwa "with (holdlock)" diperlukan untuk mencegah kesalahan kondisi sisipan / perbarui.
Saya berasumsi bahwa dua utas berbeda mengunci baris (atau halaman) dalam urutan yang berbeda ketika mereka memvalidasi kendala, dan dengan demikian menemui jalan buntu.
Apakah ini asumsi yang tepat?
Apa cara terbaik untuk mengatasi situasi ini (yaitu tidak ada deadlock, dampak minimum pada kinerja multi-threaded)?
(Jika Anda melihat gambar di tab baru, itu dapat dibaca. Maaf untuk ukurannya yang kecil.)
- Paling banyak ada 28 baris dalam @datatable.
- Saya telah menelusuri kembali kode tersebut, dan saya tidak dapat melihat di mana pun kami memulai transaksi di sini.
- Kunci asing diatur untuk kaskade hanya pada penghapusan, dan tidak ada penghapusan dari tabel induk.
Tidak akan ada masalah jika variabel tabel hanya memiliki satu nilai. Dengan beberapa baris, ada kemungkinan baru untuk kebuntuan. Misalkan dua proses bersamaan (A & B) dijalankan dengan variabel tabel yang berisi (1, 2) dan (2, 1) untuk perusahaan yang sama.
Proses A membaca tujuan, tidak menemukan baris, dan memasukkan nilai '1'. Ini memegang kunci baris eksklusif pada nilai '1'. Proses B membaca tujuan, tidak menemukan baris, dan memasukkan nilai '2'. Itu memegang kunci baris eksklusif pada nilai '2'.
Sekarang proses A perlu memproses baris 2, dan proses B perlu memproses baris 1. Tidak ada proses yang dapat membuat kemajuan karena membutuhkan kunci yang tidak kompatibel dengan kunci eksklusif yang dipegang oleh proses lain.
Untuk menghindari kebuntuan dengan beberapa baris, baris harus diproses (dan tabel diakses) dalam urutan yang sama setiap waktu . Variabel tabel dalam rencana eksekusi yang ditunjukkan dalam pertanyaan adalah heap, jadi baris tidak memiliki urutan intrinsik (mereka cenderung dibaca dalam urutan penyisipan, meskipun ini tidak dijamin):
Kurangnya urutan pemrosesan baris yang konsisten mengarah langsung ke peluang kebuntuan. Pertimbangan kedua adalah bahwa kurangnya jaminan keunikan kunci berarti bahwa Spool Tabel diperlukan untuk memberikan Perlindungan Halloween yang benar. Spool adalah spool bersemangat, artinya semua baris ditulis ke meja kerja tempdb sebelum dibaca kembali dan diputar ulang untuk operator Insert.
Mendefinisikan ulang
TYPE
variabel tabel untuk menyertakan sebuah clusterPRIMARY KEY
:Rencana pelaksanaan sekarang menunjukkan pemindaian indeks berkerumun dan jaminan keunikan berarti pengoptimal dapat menghapus Spool Tabel dengan aman:
Dalam tes dengan 5000 iterasi
MERGE
pernyataan di 128 utas, tidak ada deadlock terjadi dengan variabel tabel berkerumun. Saya harus menekankan bahwa ini hanya berdasarkan pengamatan; variabel tabel berkerumun juga bisa (secara teknis ) menghasilkan baris dalam berbagai pesanan, tetapi kemungkinan pesanan yang konsisten sangat sangat ditingkatkan. Perilaku yang diamati perlu diuji ulang untuk setiap pembaruan kumulatif baru, paket layanan, atau versi baru dari SQL Server, tentu saja.Dalam hal definisi variabel tabel tidak dapat diubah, ada alternatif lain:
Ini juga mencapai penghapusan spool (dan konsistensi baris-urutan) dengan biaya memperkenalkan semacam eksplisit:
Rencana ini juga tidak menghasilkan kebuntuan menggunakan tes yang sama. Skrip reproduksi di bawah ini:
sumber
Saya pikir SQL_Kiwi memberikan analisis yang sangat bagus. Jika Anda perlu menyelesaikan masalah dalam database, Anda harus mengikuti sarannya. Tentu saja Anda perlu menguji ulang bahwa itu masih berfungsi untuk Anda setiap kali Anda meningkatkan, menerapkan paket layanan, atau menambah / mengubah indeks atau tampilan yang diindeks.
Ada tiga alternatif lain:
Anda dapat membuat cerita berseri seri sehingga tidak bertabrakan: Anda dapat menggunakan sp_getapplock pada awal transaksi Anda dan mendapatkan kunci eksklusif sebelum mengeksekusi MERGE Anda. Tentu Anda masih perlu stress untuk mengujinya.
Anda dapat memiliki satu utas menangani semua sisipan Anda, sehingga server aplikasi Anda menangani konkurensi.
Anda dapat secara otomatis mencoba lagi setelah kebuntuan - ini mungkin pendekatan yang paling lambat jika konkurensi tinggi.
Either way, hanya Anda yang dapat menentukan dampak dari solusi Anda pada kinerja.
Biasanya kita tidak memiliki kebuntuan dalam sistem kita sama sekali, meskipun kita memiliki banyak potensi untuk memilikinya. Pada 2011 kami melakukan kesalahan dalam satu penyebaran dan memiliki setengah lusin kebuntuan terjadi dalam beberapa jam, semuanya mengikuti skenario yang sama. Saya memperbaikinya segera dan itu semua adalah jalan buntu untuk tahun ini.
Kami sebagian besar menggunakan pendekatan 1 dalam sistem kami. Ini bekerja sangat baik untuk kita.
sumber
Satu pendekatan lain yang mungkin - Saya telah menemukan Gabung untuk terkadang menghadirkan masalah penguncian dan kinerja - mungkin ada baiknya bermain dengan opsi kueri Opsi (MaxDop x)
Dalam SQL Server masa lalu redup dan jauh memiliki opsi Penguncian Baris Baris - tetapi ini tampaknya telah mati, namun PK berkerumun dengan identitas harus membuat sisipan berjalan bersih.
sumber