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.