Mengelola konkurensi saat menggunakan pola SELECT-UPDATE

25

Katakanlah Anda memiliki kode berikut (abaikan saja itu buruk):

BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else

Bagi saya, ini BUKAN mengelola konkurensi dengan benar. Hanya karena Anda melakukan transaksi tidak berarti orang lain tidak akan membaca nilai yang sama dengan yang Anda lakukan sebelum Anda mendapatkan pernyataan pembaruan Anda.

Sekarang, biarkan kode apa adanya (saya menyadari ini lebih baik ditangani sebagai satu pernyataan atau bahkan lebih baik menggunakan kolom autoincrement / identitas) apa cara yang pasti untuk membuatnya menangani konkurensi dengan benar dan mencegah kondisi balapan yang memungkinkan dua klien untuk mendapatkan yang sama nilai id?

Saya cukup yakin bahwa menambahkan WITH (UPDLOCK, HOLDLOCK)ke SELECT akan melakukan trik. The tingkat isolasi transaksi Serializable akan tampaknya bekerja juga karena itu menyangkal orang lain untuk membaca apa yang Anda lakukan sampai tran berakhir ( UPDATE :. Ini salah See Martin jawaban). Benarkah? Apakah keduanya akan bekerja dengan baik? Apakah yang satu lebih disukai daripada yang lain?

Bayangkan melakukan sesuatu yang lebih sah daripada pembaruan ID - beberapa perhitungan berdasarkan pembacaan yang perlu Anda perbarui. Mungkin ada banyak tabel yang terlibat, beberapa di antaranya akan Anda tulis dan yang lain tidak. Apa praktik terbaik di sini?

Setelah menulis pertanyaan ini, saya pikir petunjuk kunci lebih baik karena Anda hanya mengunci tabel yang Anda butuhkan, tapi saya menghargai masukan siapa pun.

PS Dan tidak, saya tidak tahu jawaban terbaik dan benar-benar ingin mendapatkan pemahaman yang lebih baik! :)

ErikE
sumber
Hanya untuk klarifikasi: apakah Anda ingin mencegah 2 klien membaca nilai yang sama atau mengeluarkan updateyang mungkin didasarkan pada data yang sudah usang? Jika yang terakhir, Anda dapat menggunakan rowversionkolom untuk memeriksa apakah baris yang akan diperbarui belum diubah sejak dibaca.
a1ex07
Kami tidak ingin klien kedua mendapatkan nilai id lama sebelum diperbarui ke nilai baru oleh klien pertama. Itu harus diblokir.
ErikE

Jawaban:

11

Hanya menangani SERIALIZABLEaspek level isolasi. Ya ini akan berhasil tetapi dengan risiko jalan buntu.

Dua transaksi akan dapat membaca baris secara bersamaan. Mereka tidak akan memblokir satu sama lain karena mereka akan mengambil Skunci objek atau RangeS-Skunci indeks tergantung pada struktur tabel dan kunci ini kompatibel . Tetapi mereka akan memblokir satu sama lain ketika mencoba untuk mendapatkan kunci yang diperlukan untuk pembaruan ( IXkunci objek atau indeks RangeS-Umasing-masing) yang akan menyebabkan kebuntuan.

Penggunaan UPDLOCKpetunjuk eksplisit sebagai gantinya akan membuat serial bacaan sehingga menghindari risiko kebuntuan.

Martin Smith
sumber
+1 tetapi: untuk heap tables Anda masih bisa mendapatkan kebuntuan konversi bahkan dengan kunci pembaruan: sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/11/…
AK
Aneh, @alex. Saya membayangkan itu ada hubungannya dengan kondisi balapan mesin yang berusaha menemukan apa yang harus dikunci sebelum benar-benar MENGUNCI itu ...
ErikE
@ErikE - Kebuntuan konversi dalam artikel Alex berubah dari IXmenjadi Xdi tumpukan itu sendiri. Menariknya tidak ada baris yang memenuhi syarat sehingga tidak ada kunci baris yang dapat dikeluarkan. Tidak yakin mengapa dibutuhkan Xkunci sama sekali.
Martin Smith
11

Saya pikir pendekatan terbaik bagi Anda adalah dengan benar-benar mengekspos modul Anda ke konkurensi tinggi dan lihat sendiri. Terkadang UPDLOCK saja sudah cukup, dan tidak perlu untuk HOLDLOCK. Terkadang sp_getapplock bekerja dengan sangat baik. Saya tidak akan membuat pernyataan selimut di sini - kadang menambahkan satu indeks lagi, pemicu, atau tampilan indeks mengubah hasilnya. Kita perlu menekankan kode uji dan melihat sendiri berdasarkan kasus per kasus.

Saya telah menulis beberapa contoh pengujian stres di sini

Sunting: untuk pengetahuan internal yang lebih baik, Anda dapat membaca buku-buku Kalen Delaney. Namun, buku mungkin tidak sinkron seperti dokumentasi lainnya. Selain itu, ada terlalu banyak kombinasi untuk dipertimbangkan: enam tingkat isolasi, banyak jenis kunci, indeks berkerumun / nonclustered dan siapa yang tahu apa lagi. Itu banyak kombinasi. Selain itu, SQL Server adalah sumber tertutup, jadi kami tidak dapat mengunduh kode sumber, men-debug-nya dan semacamnya - yang akan menjadi sumber utama pengetahuan. Hal lain mungkin tidak lengkap atau ketinggalan jaman setelah rilis berikutnya atau paket layanan.

Jadi, Anda tidak boleh memutuskan apa yang berfungsi untuk sistem Anda tanpa pengujian stres Anda sendiri. Apa pun yang Anda baca, ini dapat membantu Anda memahami apa yang sedang terjadi, tetapi Anda harus membuktikan bahwa saran yang Anda baca bermanfaat untuk Anda. Saya tidak berpikir ada yang bisa melakukannya untuk Anda.

AK
sumber
9

Dalam kasus khusus ini, penambahan UPDLOCKkunci pada kunci SELECTmemang akan mencegah anomali. Penambahan HOLDLOCKtidak diperlukan karena kunci pembaruan diadakan selama durasi transaksi, tetapi saya akui untuk memasukkannya sendiri sebagai kebiasaan (mungkin buruk) di masa lalu.

Bayangkan melakukan sesuatu yang lebih sah daripada pembaruan ID, beberapa perhitungan berdasarkan pembacaan yang perlu Anda perbarui. Mungkin ada banyak tabel yang terlibat, beberapa di antaranya akan Anda tulis dan yang lain tidak. Apa praktik terbaik di sini?

Tidak ada praktik terbaik. Pilihan Anda untuk kontrol konkurensi harus didasarkan pada persyaratan aplikasi. Beberapa aplikasi / transaksi perlu dieksekusi seolah-olah mereka memiliki kepemilikan eksklusif atas basis data, menghindari anomali dan ketidakakuratan di semua biaya. Aplikasi / transaksi lain dapat mentolerir beberapa tingkat gangguan dari satu sama lain.

  • Mengambil tingkat stok terbalut (<5, 10+, 50+, 100+) untuk produk di toko web = pembacaan kotor (tidak akurat tidak masalah).
  • Memeriksa dan mengurangi tingkat stok pada checkout toko web itu = membaca berulang (kami HARUS memiliki stok sebelum kami menjual, kami HARUS tidak berakhir dengan tingkat stok negatif).
  • Memindahkan uang tunai antara rekening saya saat ini dan tabungan di bank = serializable (jangan salah menghitung atau salah menaruhkan uang saya!).

Edit: @ komentar AlexKuznetsov mendorong saya membaca kembali pertanyaan dan menghapus kesalahan yang sangat jelas dalam jawaban saya. Catatan untuk diri sendiri pada posting larut malam.

Mark Storey-Smith
sumber