Mengapa CTE terbuka untuk pembaruan yang hilang?

8

Saya tidak mengerti apa yang dimaksud Craig Ringer ketika dia berkomentar:

Solusi ini dapat mengalami pembaruan yang hilang jika transaksi memasukkan kembali; tidak ada pemeriksaan untuk memastikan bahwa UPDATE memengaruhi setiap baris.

di https://stackoverflow.com/a/8702291/14731 . Harap berikan urutan contoh acara (mis. Thread 1 melakukan X, Thread 2 melakukan Y) yang menunjukkan bagaimana pembaruan yang hilang dapat terjadi.

Gili
sumber
1
Tanyakan sesuatu tentang komentar yang saya tinggalkan setahun yang lalu pada topik yang kompleks ... menyenangkan! Sekarang saya harus mengingat apa masalah sebenarnya. Periksa kembali sekarang.
Craig Ringer

Jawaban:

14

Saya pikir saya mungkin bermaksud menambahkan komentar itu pada jawaban sebelumnya, tentang dua pernyataan terpisah. Sudah lebih dari setahun yang lalu, jadi saya tidak sepenuhnya yakin lagi.

Kueri berbasis wCTE tidak benar-benar menyelesaikan masalah yang seharusnya, tetapi setelah meninjaunya kembali lebih dari setahun kemudian saya tidak melihat kemungkinan pembaruan yang hilang dalam versi wCTE.

(Perhatikan bahwa semua solusi ini hanya akan berfungsi dengan baik jika Anda mencoba mengubah tepat satu baris pada setiap transaksi. Segera setelah Anda mencoba untuk melakukan beberapa perubahan dalam satu transaksi, semuanya menjadi berantakan karena kebutuhan untuk mencoba lagi loop pada rollback. Minimal Anda harus menggunakan savepoint di antara setiap perubahan.)

Versi dua pernyataan tunduk pada pembaruan yang hilang.

Versi yang menggunakan dua pernyataan terpisah dapat mengalami pembaruan yang hilang kecuali aplikasi memeriksa jumlah baris yang terpengaruh dari UPDATEpernyataan dan INSERTpernyataan dan mencoba lagi jika keduanya nol.

Bayangkan apa yang terjadi jika Anda memiliki dua transaksi secara READ COMMITTEDterpisah.

  • TX1 menjalankan UPDATE(tidak ada efek)
  • TX1 menjalankan INSERT(menyisipkan baris)
  • TX2 menjalankan UPDATE(tidak ada efek, baris yang dimasukkan oleh TX1 belum terlihat)
  • TX1 COMMITs.
  • TX2 menjalankan INSERT, * yang mendapat snapshot baru yang dapat melihat baris yang dilakukan oleh TX1. The EXISTSklausul pengembalian benar, karena TX2 sekarang dapat melihat baris dimasukkan oleh TX1.

Jadi TX2 tidak berpengaruh. Kecuali jika aplikasi memeriksa jumlah baris dari pembaruan dan sisipan dan coba lagi jika keduanya melaporkan nol baris, ia tidak akan tahu bahwa transaksi tidak berpengaruh dan akan melanjutkan dengan gembira.

Satu-satunya cara ia dapat memeriksa jumlah baris yang terpengaruh adalah menjalankannya sebagai dua pernyataan terpisah dan bukan multi-pernyataan, atau menggunakan prosedur.

Anda dapat menggunakan SERIALIZABLEisolasi, tetapi Anda masih perlu mengulangi loop untuk menghadapi kegagalan serialisasi.

Versi wCTE melindungi terhadap masalah pembaruan yang hilang karena INSERTtergantung pada apakah UPDATEmempengaruhi setiap baris, bukan pada permintaan yang terpisah.

WCTE tidak menghilangkan pelanggaran unik

Versi CTE yang dapat ditulisi masih belum bisa diandalkan.

Pertimbangkan dua transaksi yang menjalankan ini secara bersamaan.

  • Keduanya menjalankan klausa VALUES.

  • Sekarang keduanya menjalankan UPDATEporsi. Karena tidak ada baris yang cocok dengan UPDATEklausa s where, keduanya mengembalikan resultset kosong dari pembaruan dan tidak membuat perubahan.

  • Sekarang keduanya menjalankan INSERTporsinya. Karena UPDATEbaris nol yang dikembalikan untuk kedua kueri, keduanya mencoba ke INSERTbaris.

Satu berhasil. Satu melemparkan pelanggaran unik dan batal.

Ini bukan alasan untuk khawatir tentang kehilangan data selama aplikasi memeriksa hasil kesalahan dari kueri-nya (yaitu aplikasi apa pun yang ditulis dengan sopan) dan mencoba ulang, tetapi itu membuat solusinya tidak lebih baik daripada versi dua pernyataan yang ada. Itu tidak menghilangkan kebutuhan untuk mengulangi loop.

Keuntungan yang ditawarkan wCTE dibandingkan versi dua pernyataan yang ada adalah bahwa ia menggunakan output dari UPDATEuntuk memutuskan apakah akan INSERT, daripada menggunakan kueri terpisah terhadap tabel. Itu sebagian optimasi, tetapi sebagian melindungi terhadap masalah dengan versi dua pernyataan yang menyebabkan pembaruan hilang; Lihat di bawah.

Anda dapat menjalankan wCTE secara SERIALIZABLEterpisah, tetapi kemudian Anda hanya akan mendapatkan kegagalan serialisasi alih-alih pelanggaran unik. Itu tidak akan mengubah kebutuhan untuk mengulangi loop.

WCTE tampaknya tidak rentan terhadap pembaruan yang hilang

Komentar saya menyarankan bahwa solusi ini dapat mengakibatkan pembaruan yang hilang, tetapi setelah meninjau bahwa saya pikir saya mungkin salah.

Sudah lebih dari setahun yang lalu, dan saya tidak dapat mengingat keadaan yang tepat, tetapi saya pikir saya mungkin melewatkan fakta bahwa indeks unik memiliki sebagian pengecualian dari aturan visibilitas transaksi untuk memungkinkan satu transaksi memasukkan menunggu yang lain untuk memasukkan atau memutar kembali sebelum melanjutkan.

Atau mungkin saya melewatkan fakta bahwa INSERTdalam WCTE tergantung pada apakah UPDATEbaris yang terpengaruh, bukan pada apakah baris kandidat ada di tabel.

Konflik INSERTpada indeks unik menunggu komit / kembalikan

Katakan bahwa satu salinan kueri berjalan, menyisipkan baris. Perubahan belum dilakukan. Tupel baru ada di heap dan indeks unik, tetapi belum terlihat oleh transaksi lain, terlepas dari tingkat isolasi.

Sekarang salinan kueri lain berjalan. Baris yang dimasukkan belum terlihat karena salinan pertama belum dilakukan, sehingga pembaruan tidak cocok dengan apa pun. Kueri akan melanjutkan untuk mencoba menyisipkan, yang akan melihat bahwa transaksi lain yang sedang berlangsung sedang memasukkan kunci yang sama dan akan memblokir menunggu transaksi itu dilakukan atau dibatalkan .

Jika transaksi pertama dilakukan, yang kedua akan gagal dengan pelanggaran unik, per di atas. Jika transaksi pertama gulung kembali maka yang kedua akan melanjutkan dengan memasukkannya sebagai gantinya.

The INSERTtergantung pada UPDATErowcount melindungi terhadap update yang hilang

Tidak seperti dalam kasus dua pernyataan, saya tidak berpikir WCTE rentan terhadap pembaruan yang hilang.

Jika UPDATEtidak memiliki efek, itu INSERTakan selalu berjalan, karena itu sangat tergantung pada apakah UPDATEmelakukan sesuatu, bukan pada keadaan tabel eksternal. Jadi masih bisa gagal dengan pelanggaran unik, tetapi tidak bisa diam-diam gagal untuk memiliki efek dan kehilangan pembaruan sepenuhnya.

Craig Ringer
sumber