Mat dan Erwin keduanya benar, dan saya hanya menambahkan jawaban lain untuk memperluas apa yang mereka katakan dengan cara yang tidak sesuai dengan komentar. Karena jawaban mereka tampaknya tidak memuaskan semua orang, dan ada saran bahwa pengembang PostgreSQL harus dikonsultasikan, dan saya adalah satu, saya akan menguraikan.
Poin penting di sini adalah bahwa di bawah standar SQL, dalam transaksi yang berjalan di READ COMMITTED
tingkat isolasi transaksi, batasannya adalah bahwa pekerjaan transaksi yang tidak dikomit tidak boleh terlihat. Ketika pekerjaan transaksi yang dilakukan menjadi terlihat tergantung pada implementasi. Apa yang Anda tunjukkan adalah perbedaan dalam bagaimana dua produk telah memilih untuk mengimplementasikannya. Tidak ada implementasi yang melanggar persyaratan standar.
Inilah yang terjadi di dalam PostgreSQL, secara terperinci:
S1-1 berjalan (1 baris dihapus)
Baris lama dibiarkan di tempat, karena S1 mungkin masih memutar kembali, tetapi S1 sekarang memegang kunci pada baris sehingga sesi lain yang mencoba mengubah baris akan menunggu untuk melihat apakah S1 berkomitmen atau mundur. Setiap bacaan tabel masih dapat melihat baris lama, kecuali jika mereka berusaha menguncinya dengan SELECT FOR UPDATE
atau SELECT FOR SHARE
.
Berjalan S2-1 (tetapi diblokir karena S1 memiliki kunci tulis)
S2 sekarang harus menunggu untuk melihat hasil S1. Jika S1 mundur daripada komit, S2 akan menghapus baris. Perhatikan bahwa jika S1 memasukkan versi baru sebelum memutar kembali, versi baru tidak akan pernah ada dari perspektif transaksi lain, juga versi lama tidak akan dihapus dari perspektif transaksi lainnya.
S1-2 berjalan (1 baris disisipkan)
Baris ini tidak tergantung pada yang lama. Jika ada pembaruan baris dengan id = 1, versi lama dan baru akan terkait, dan S2 dapat menghapus versi terbaru dari baris ketika menjadi tidak terblokir. Bahwa baris baru kebetulan memiliki nilai yang sama dengan beberapa baris yang ada di masa lalu tidak menjadikannya sama dengan versi terbaru dari baris itu.
S1-3 berjalan, melepaskan kunci tulis
Jadi perubahan S1 tetap ada. Satu baris hilang. Satu baris telah ditambahkan.
Berjalan S2-1, sekarang bisa mendapatkan kunci. Tetapi laporan 0 baris dihapus. HAH???
Apa yang terjadi secara internal, adalah bahwa ada penunjuk dari satu versi baris ke versi berikutnya dari baris yang sama jika diperbarui. Jika baris dihapus, tidak ada versi berikutnya. Ketika READ COMMITTED
transaksi terbangun dari blok pada konflik tulis, itu mengikuti rantai pembaruan itu sampai akhir; jika baris belum dihapus dan jika masih memenuhi kriteria pemilihan kueri itu akan diproses. Baris ini telah dihapus, sehingga kueri S2 melanjutkan.
S2 mungkin atau mungkin tidak sampai ke baris baru selama pemindaian tabel. Jika ya, ia akan melihat bahwa baris baru dibuat setelah DELETE
pernyataan S2 dimulai, dan juga bukan bagian dari rangkaian baris yang terlihat.
Jika PostgreSQL me-restart seluruh pernyataan DELETE S2 dari awal dengan snapshot baru, itu akan berperilaku sama dengan SQL Server. Komunitas PostgreSQL tidak memilih untuk melakukan itu karena alasan kinerja. Dalam kasus sederhana ini Anda tidak akan pernah melihat perbedaan dalam kinerja, tetapi jika Anda sepuluh juta baris menjadi DELETE
ketika Anda diblokir, Anda pasti akan melihatnya. Ada trade-off di sini di mana PostgreSQL telah memilih kinerja, karena versi yang lebih cepat masih memenuhi persyaratan standar.
Berjalan S2-2, melaporkan pelanggaran batasan kunci yang unik
Tentu saja, barisnya sudah ada. Ini adalah bagian paling tidak mengejutkan dari gambar ini.
Meskipun ada beberapa perilaku mengejutkan di sini, semuanya sesuai dengan standar SQL dan dalam batas-batas apa yang "khusus implementasi" sesuai dengan standar. Tentu dapat mengejutkan jika Anda mengasumsikan bahwa beberapa perilaku implementasi lain akan hadir di semua implementasi, tetapi PostgreSQL berusaha sangat keras untuk menghindari kegagalan serialisasi di READ COMMITTED
tingkat isolasi, dan memungkinkan beberapa perilaku yang berbeda dari produk lain untuk mencapai itu.
Sekarang, secara pribadi saya bukan penggemar READ COMMITTED
tingkat isolasi transaksi dalam implementasi produk apa pun . Mereka semua memungkinkan kondisi ras untuk menciptakan perilaku yang mengejutkan dari sudut pandang transaksional. Begitu seseorang menjadi terbiasa dengan perilaku aneh yang diizinkan oleh satu produk, mereka cenderung menganggap itu "normal" dan pengorbanan yang dipilih oleh produk lain aneh. Tetapi setiap produk harus membuat semacam trade-off untuk mode apa pun yang tidak benar-benar diterapkan SERIALIZABLE
. Di mana pengembang PostgreSQL telah memilih untuk menarik garis READ COMMITTED
adalah untuk meminimalkan pemblokiran (membaca jangan memblokir menulis dan menulis jangan memblokir membaca) dan untuk meminimalkan kemungkinan kegagalan serialisasi.
Standar ini mensyaratkan bahwa SERIALIZABLE
transaksi menjadi default, tetapi sebagian besar produk tidak melakukan itu karena hal itu menyebabkan kinerja lebih tinggi dari tingkat isolasi transaksi yang lebih longgar. Beberapa produk bahkan tidak memberikan transaksi yang benar-benar serial ketika SERIALIZABLE
dipilih - terutama Oracle dan versi PostgreSQL sebelum 9.1. Tetapi menggunakan SERIALIZABLE
transaksi yang sebenarnya adalah satu-satunya cara untuk menghindari efek mengejutkan dari kondisi balapan, dan SERIALIZABLE
transaksi selalu harus diblokir untuk menghindari kondisi balapan atau memutar kembali beberapa transaksi untuk menghindari kondisi balapan yang berkembang. Implementasi SERIALIZABLE
transaksi yang paling umum adalah Strict Two-Phase Locking (S2PL) yang memiliki kegagalan pemblokiran dan serialisasi (dalam bentuk deadlock).
Pengungkapan penuh: Saya bekerja dengan Dan Ports dari MIT untuk menambahkan transaksi yang benar-benar serial ke PostgreSQL versi 9.1 menggunakan teknik baru yang disebut Serializable Snapshot Isolasi.
READ COMMITTED
transaksi, Anda memiliki kondisi balapan: apa yang akan terjadi jika transaksi lain menyisipkan baris baru setelah yang pertamaDELETE
dimulai dan sebelum yang keduaDELETE
dimulai? Dengan transaksi yang tidak seketatSERIALIZABLE
dua cara utama untuk menutup kondisi balapan adalah melalui promosi konflik (tapi itu tidak membantu ketika baris dihapus) dan terwujudnya konflik. Anda bisa mematerialisasi konflik dengan memiliki tabel "id" yang diperbarui untuk setiap baris yang dihapus, atau dengan mengunci tabel secara eksplisit. Atau gunakan coba lagi pada kesalahan.Saya percaya ini adalah desain, sesuai dengan deskripsi tingkat isolasi read-commit untuk PostgreSQL 9.2:
Baris Anda memasukkan dalam
S1
tidak ada namun ketikaS2
'sDELETE
mulai. Jadi itu tidak akan terlihat oleh deleteS2
seperti pada ( 1 ) di atas. Salah satu yangS1
dihapus diabaikan olehS2
'sDELETE
sesuai dengan ( 2 ).Jadi
S2
, hapus tidak melakukan apa-apa. Ketika sisipan muncul, orang tersebut melihatS1
sisipan:Jadi upaya memasukkan
S2
gagal dengan kendala kendala.Melanjutkan membaca dokumen itu, menggunakan pembacaan berulang atau bahkan serializable tidak akan menyelesaikan masalah Anda sepenuhnya - sesi kedua akan gagal dengan kesalahan serialisasi pada penghapusan.
Ini akan memungkinkan Anda untuk mencoba kembali transaksi.
sumber
Saya sepenuhnya setuju dengan jawaban yang sangat baik dari @ Mat . Saya hanya menulis jawaban lain, karena tidak akan cocok dengan komentar.
Sebagai balasan untuk komentar Anda:
DELETE
S2 dalam sudah terhubung pada versi baris tertentu. Karena ini dibunuh oleh S1 sementara itu, S2 menganggap dirinya berhasil. Meskipun tidak jelas dari pandangan sekilas, rangkaian acara sebenarnya adalah seperti ini:Semuanya dengan desain. Anda benar-benar perlu menggunakan
SERIALIZABLE
transaksi untuk kebutuhan Anda dan pastikan Anda mencoba lagi pada kegagalan serialisasi.sumber
Gunakan kunci utama DEFERRABLE dan coba lagi.
sumber
Kami juga menghadapi masalah ini. Solusi kami menambahkan
select ... for update
sebelumnyadelete from ... where
. Level isolasi harus Baca Komitmen.sumber