Saya memiliki database Postgres yang berisi perincian tentang kelompok server, seperti status server ('aktif', 'siaga' dll). Server aktif kapan saja mungkin perlu gagal ke siaga, dan saya tidak peduli siaga mana yang digunakan secara khusus.
Saya ingin permintaan basis data untuk mengubah status siaga - JUST ONE - dan mengembalikan IP server yang akan digunakan. Pilihannya dapat arbitrer: karena status server berubah dengan kueri, tidak masalah siaga mana yang dipilih.
Apakah mungkin membatasi permintaan saya hanya untuk satu pembaruan?
Inilah yang saya miliki sejauh ini:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
Postgres tidak menyukai ini. Apa yang bisa saya lakukan secara berbeda?
postgresql
update
concurrency
queue
jauh sekaliupormanorman
sumber
sumber
Jawaban:
Tanpa akses tulis bersamaan
Wujudkan seleksi dalam CTE dan gabungkan ke dalam
FROM
klausaUPDATE
.Saya awalnya memiliki subquery sederhana di sini, tetapi itu dapat menghindari
LIMIT
untuk rencana permintaan tertentu seperti yang ditunjukkan Feike :Atau gunakan subquery berkorelasi rendahuntuk kasus sederhana dengan
LIMIT
1
. Lebih sederhana, lebih cepat:Dengan akses tulis bersamaan
Dengan asumsi tingkat isolasi standar
READ COMMITTED
untuk semua ini. Level isolasi yang lebih ketat (REPEATABLE READ
danSERIALIZABLE
) masih dapat mengakibatkan kesalahan serialisasi. Lihat:Di bawah beban tulis bersamaan, tambahkan
FOR UPDATE SKIP LOCKED
untuk mengunci baris untuk menghindari kondisi balapan.SKIP LOCKED
ditambahkan di Postgres 9.5 , untuk versi yang lebih lama lihat di bawah. Manual:Jika tidak ada kualifikasi, baris terbuka yang tersisa, tidak ada yang terjadi dalam permintaan ini (tidak ada baris diperbarui) dan Anda mendapatkan hasil kosong. Untuk operasi tidak kritis itu berarti Anda selesai.
Namun, transaksi bersamaan mungkin telah mengunci baris, tetapi kemudian tidak menyelesaikan pembaruan (
ROLLBACK
atau alasan lainnya). Yang pasti jalankan pemeriksaan terakhir:SELECT
juga melihat baris yang terkunci. Sementara itu tidak kembalitrue
, satu atau lebih baris sedang diproses dan transaksi masih bisa dibatalkan. (Atau baris baru telah ditambahkan sementara itu.) Tunggu sebentar, kemudian lingkarkan kedua langkah: (UPDATE
sampai Anda tidak mendapatkan baris kembali;SELECT
...) sampai Anda mendapatkannyatrue
.Terkait:
Tanpa
SKIP LOCKED
di PostgreSQL 9.4 atau lebih lamaTransaksi bersamaan yang mencoba mengunci baris yang sama diblokir sampai yang pertama melepaskan kuncinya.
Jika yang pertama dibatalkan, transaksi berikutnya mengambil kunci dan hasil secara normal; yang lain dalam antrian terus menunggu.
Jika komitmen pertama,
WHERE
kondisi dievaluasi kembali dan jika tidakTRUE
lagi (status
telah berubah), CTE (agak mengejutkan) tidak mengembalikan baris. Tidak ada yang terjadi. Itu perilaku yang diinginkan ketika semua transaksi ingin memperbarui yang sama berturut-turut .Tapi tidak ketika setiap transaksi ingin memperbarui yang berikutnya berturut-turut . Dan karena kami hanya ingin memperbarui baris yang arbitrer (atau acak ) , tidak ada gunanya menunggu sama sekali.
Kami dapat membuka blokir situasi dengan bantuan kunci penasihat :
Dengan cara ini, baris berikutnya yang belum dikunci akan diperbarui. Setiap transaksi mendapat baris baru untuk dikerjakan. Saya mendapat bantuan dari Czech Postgres Wiki untuk trik ini.
id
menjadibigint
kolom unik apa pun (atau jenis apa pun dengan gips sepertiint4
atauint2
)Jika kunci penasihat digunakan untuk beberapa tabel dalam database Anda secara bersamaan, jelaskan dengan
pg_try_advisory_xact_lock(tableoid::int, id)
-id
menjadi unik diinteger
sini.Sejak
tableoid
adalahbigint
kuantitas, dapat secara teoritis melimpahinteger
. Jika Anda cukup paranoid, gunakan(tableoid::bigint % 2147483648)::int
- meninggalkan "tabrakan hash" teoretis untuk ...Juga, Postgres bebas untuk menguji
WHERE
kondisi dalam urutan apa pun. Itu bisa mengujipg_try_advisory_xact_lock()
dan mendapatkan kunci sebelumnyastatus = 'standby'
, yang dapat menghasilkan kunci penasihat tambahan pada baris yang tidak terkait, di manastatus = 'standby'
tidak benar. Pertanyaan terkait pada SO:Biasanya, Anda bisa mengabaikan ini. Untuk menjamin bahwa hanya baris yang memenuhi syarat yang dikunci, Anda dapat membuat sarang predikat dalam CTE seperti di atas atau subquery dengan
OFFSET 0
peretasan (mencegah inlining) . Contoh:Atau (lebih murah untuk pemindaian berurutan) memeriksa kondisi dalam
CASE
pernyataan seperti:Namun para
CASE
trik juga akan tetap Postgres menggunakan indeks padastatus
. Jika indeks seperti itu tersedia, Anda tidak perlu bersarang ekstra untuk memulai: hanya baris yang memenuhi syarat yang akan dikunci dalam pemindaian indeks.Karena Anda tidak dapat memastikan bahwa indeks digunakan dalam setiap panggilan, Anda bisa saja:
Secara
CASE
logis berlebihan, tetapi server tujuan yang dibahas.Jika perintah adalah bagian dari transaksi panjang, pertimbangkan kunci tingkat sesi yang dapat (dan harus) dilepaskan secara manual. Jadi, Anda dapat membuka kunci segera setelah selesai dengan baris yang terkunci:
pg_try_advisory_lock()
danpg_advisory_unlock()
. Manual:Terkait:
sumber