Saya perlu menyimpan nomor revisi unik (per-baris) di tabel document_revisions, di mana nomor revisi dimasukkan ke dokumen, jadi tidak unik untuk seluruh tabel, hanya untuk dokumen terkait.
Saya awalnya datang dengan sesuatu seperti:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Tetapi ada kondisi balapan!
Saya mencoba menyelesaikannya pg_advisory_lock
, tetapi dokumentasinya agak langka dan saya tidak sepenuhnya memahaminya, dan saya tidak ingin mengunci sesuatu karena kesalahan.
Apakah yang berikut dapat diterima, atau saya salah, atau apakah ada solusi yang lebih baik?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Tidakkah seharusnya saya mengunci baris dokumen (key1) untuk operasi yang diberikan (key2)? Jadi itu akan menjadi solusi yang tepat:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Mungkin saya tidak terbiasa dengan PostgreSQL dan SERIAL dapat dicakup, atau mungkin secara berurutan dan nextval()
akan melakukan pekerjaan dengan lebih baik?
sumber
Jawaban:
Dengan anggapan Anda menyimpan semua revisi dokumen dalam sebuah tabel, pendekatannya adalah tidak menyimpan nomor revisi tetapi menghitungnya berdasarkan jumlah revisi yang disimpan dalam tabel.
Ini pada dasarnya adalah nilai turunan , bukan sesuatu yang perlu Anda simpan.
Fungsi jendela dapat digunakan untuk menghitung angka revisi, seperti
dan Anda akan membutuhkan kolom seperti
change_date
untuk melacak urutan revisi.Di sisi lain, jika Anda hanya memiliki
revision
properti dokumen dan ini menunjukkan "berapa kali dokumen telah berubah", maka saya akan menggunakan pendekatan penguncian yang optimis, seperti:Jika ini memperbarui 0 baris, maka telah ada pembaruan menengah dan Anda perlu memberi tahu pengguna ini.
Secara umum, cobalah untuk menjaga solusi Anda sesederhana mungkin. Dalam hal ini oleh
update
pernyataan tunggal alih-alihselect
diikuti olehinsert
atauupdate
sumber
SEQUENCE dijamin unik, dan case-use Anda terlihat berlaku jika jumlah dokumen Anda tidak terlalu tinggi (kalau tidak, Anda memiliki banyak urutan untuk dikelola). Gunakan klausa RETURNING untuk mendapatkan nilai yang dihasilkan oleh urutan. Misalnya, menggunakan 'A36' sebagai document_id:
Mengelola urutan perlu ditangani dengan hati-hati. Anda mungkin bisa menyimpan tabel terpisah yang berisi nama dokumen dan urutan yang terkait dengan itu
document_id
untuk referensi saat memasukkan / memperbaruidocument_revisions
tabel.sumber
Ini sering diselesaikan dengan penguncian optimis:
Jika pembaruan mengembalikan 0 baris yang diperbarui, Anda telah melewatkan pembaruan Anda karena orang lain telah memperbarui baris.
sumber
(Saya sampai pada pertanyaan ini ketika mencoba menemukan kembali sebuah artikel tentang topik ini. Sekarang setelah saya menemukannya, saya mempostingnya di sini kalau-kalau ada orang lain yang sedang mengejar opsi alternatif untuk jawaban yang saat ini dipilih — berjendela dengan
row_number()
)Saya memiliki kasus penggunaan yang sama. Untuk setiap catatan yang dimasukkan ke dalam proyek spesifik dalam SaaS kami, kami membutuhkan angka yang unik dan bertambah yang dapat dihasilkan dalam menghadapi data bersamaan
INSERT
dan idealnya adalah tanpa celah.Artikel ini menjelaskan solusi yang bagus , yang akan saya ringkas di sini untuk kemudahan dan keturunan.
document_id
dancounter
.counter
akan menjadiDEFAULT 0
Atau, jika Anda sudah memilikidocument
entitas yang mengelompokkan semua versi, acounter
dapat ditambahkan di sana.BEFORE INSERT
pemicu kedocument_versions
tabel yang secara atom menambah penghitung (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
) dan kemudian setelNEW.version
ke nilai penghitung itu.Atau, Anda mungkin dapat menggunakan CTE untuk melakukan ini di lapisan aplikasi (meskipun saya lebih suka itu menjadi pemicu demi konsistensi):
Ini pada prinsipnya mirip dengan bagaimana Anda mencoba menyelesaikannya pada awalnya, kecuali bahwa dengan memodifikasi baris tandingan dalam satu pernyataan, ia memblokir bacaan nilai basi hingga
INSERT
dikomit.Berikut transkrip dari
psql
menunjukkan ini dalam aksi:Seperti yang Anda lihat, Anda harus berhati-hati tentang bagaimana ini
INSERT
terjadi, karenanya versi pemicu, yang terlihat seperti ini:Itu membuat
INSERT
jauh lebih lurus ke depan dan integritas data lebih kuat dalam menghadapi yangINSERT
berasal dari sumber sewenang-wenang:sumber