Sementara saran Erwin mungkin yang paling sederhana cara untuk mendapatkan perilaku yang benar (selama Anda mencoba lagi transaksi Anda jika Anda mendapatkan pengecualian dengan SQLSTATE
dari 40001), aplikasi yang mengantre berdasarkan sifatnya cenderung bekerja lebih baik dengan permintaan yang memblokir kesempatan untuk mengambil giliran di antrian dibandingkan dengan implementasi PostgreSQL dari SERIALIZABLE
transaksi, yang memungkinkan konkurensi lebih tinggi dan agak lebih "optimis" tentang kemungkinan tabrakan.
Contoh kueri dalam pertanyaan, sebagaimana adanya, dalam default READ COMMITTED
tingkat isolasi transaksi akan memungkinkan dua (atau lebih) koneksi bersamaan ke keduanya "mengklaim" baris yang sama dari antrian. Apa yang akan terjadi adalah ini:
- T1 dimulai dan sampai sejauh mengunci baris di
UPDATE
fase. - T2 tumpang tindih dengan T1 dalam waktu eksekusi dan mencoba memperbarui baris itu. Ini memblokir menunggu
COMMIT
atauROLLBACK
dari T1. - T1 melakukan, setelah berhasil "mengklaim" baris.
- T2 mencoba memperbarui baris, menemukan bahwa T1 sudah memilikinya, mencari versi baru dari baris tersebut, menemukan bahwa baris tersebut masih memenuhi kriteria pemilihan (yang hanya
id
cocok), dan juga "mengklaim" baris.
Itu dapat dimodifikasi agar berfungsi dengan benar (jika Anda menggunakan versi PostgreSQL yang memungkinkan FOR UPDATE
klausa dalam subquery). Cukup tambahkan FOR UPDATE
ke akhir subquery yang memilih id, dan ini akan terjadi:
- T1 dimulai dan sekarang mengunci baris sebelum memilih id.
- T2 tumpang tindih dengan T1 dalam waktu eksekusi dan memblokir saat mencoba memilih id, menunggu
COMMIT
atauROLLBACK
dari T1. - T1 melakukan, setelah berhasil "mengklaim" baris.
- Pada saat T2 dapat membaca baris untuk melihat id, ia melihat bahwa ia telah diklaim, sehingga ia menemukan id berikutnya yang tersedia.
Pada bagian REPEATABLE READ
atau SERIALIZABLE
tingkat isolasi transaksi, konflik penulisan akan menimbulkan kesalahan, yang dapat Anda tangkap dan tentukan sebagai kegagalan serialisasi berdasarkan SQLSTATE, dan coba lagi.
Jika Anda umumnya menginginkan transaksi SERIALIZABLE tetapi Anda ingin menghindari percobaan ulang di area antrian, Anda mungkin dapat melakukannya dengan menggunakan kunci penasehat.