MVCC-1. Isolasi

Halo, Habr! Dengan artikel ini, saya memulai serangkaian loop (atau serangkaian seri? Secara umum, ide besar) tentang struktur internal PostgreSQL.

Materi akan didasarkan pada kursus pelatihan administrasi yang kami lakukan dengan Pavel pluzanov . Tidak semua orang suka menonton video (saya pasti tidak suka itu), tetapi membaca slide, bahkan dengan komentar, sepenuhnya “salah”.

Tentu saja, artikel tidak akan mengulangi isi dari kursus satu ke satu. Saya hanya akan berbicara tentang bagaimana semuanya berjalan, menghilangkan administrasi itu sendiri, tetapi saya akan mencoba melakukannya dengan lebih detail dan lebih detail. Dan saya percaya bahwa pengetahuan semacam itu bermanfaat bagi pengembang aplikasi tidak kurang dari administrator.

Saya akan fokus pada mereka yang sudah memiliki pengalaman menggunakan PostgreSQL dan setidaknya secara umum bayangkan apa yang terjadi. Untuk pemula, teks akan sedikit berat. Sebagai contoh, saya tidak akan mengatakan sepatah kata pun tentang cara menginstal PostgreSQL dan menjalankan psql.

Hal-hal yang akan dibahas tidak banyak berubah dari versi ke versi, tetapi saya akan menggunakan PostgreSQL 11 vanilla saat ini.

Siklus pertama dikhususkan untuk masalah yang berkaitan dengan isolasi dan multiversion, dan rencananya adalah sebagai berikut:

  1. Isolasi, sebagaimana dipahami oleh standar dan PostgreSQL (artikel ini);
  2. Lapisan, file, halaman - apa yang terjadi di tingkat fisik;
  3. Versi baris, transaksi virtual dan bersarang ;
  4. Snapshots data dan visibilitas versi baris, horizon peristiwa ;
  5. Pembersihan dalam-halaman dan pembaruan-HOT ;
  6. Pembersihan normal (vakum);
  7. Pembersihan otomatis (autovacuum);
  8. Transaksi counter overflow dan macet .

Baiklah, ayo pergi.

Apa itu isolasi dan mengapa itu penting?


Mungkin semua orang setidaknya tahu tentang keberadaan transaksi, bertemu dengan singkatan ACID dan mendengar tentang tingkat isolasi. Tetapi seseorang masih harus memenuhi pendapat bahwa ini adalah teori yang tidak perlu dalam praktek. Karena itu, saya akan meluangkan waktu mencoba menjelaskan mengapa ini sangat penting.

Tidak mungkin Anda akan senang jika aplikasi menerima data yang salah dari database, atau jika aplikasi menulis data yang salah ke database.

Tetapi apakah data yang "benar"? Diketahui bahwa pada tingkat basis data, Anda dapat membuat batasan integritas (seperti BUKAN NULL atau UNIK). Jika data selalu memenuhi kendala integritas (dan ini karena DBMS menjamin ini), maka mereka bersifat holistik.

Apakah benar dan tidak terpisahkan - hal yang sama? Tidak juga. Tidak semua batasan dapat dirumuskan di tingkat basis data. Sebagian dari pembatasan terlalu rumit, misalnya, mencakup beberapa tabel sekaligus. Dan bahkan jika pembatasan, pada prinsipnya, dapat didefinisikan dalam database, tetapi karena alasan tertentu mereka tidak melakukannya, itu tidak berarti bahwa itu dapat dilanggar.

Jadi, kebenaran lebih ketat daripada integritas , tetapi kami tidak tahu persis apa itu. Masih harus diakui bahwa standar kebenaran adalah aplikasi yang, seperti yang ingin kita yakini, ditulis dengan benar dan tidak pernah salah. Bagaimanapun, jika aplikasi tidak melanggar integritas, tetapi melanggar kebenaran, DBMS tidak akan mengetahuinya dan tidak akan menangkapnya.

Mulai sekarang, kami akan menyebut kebenaran istilah konsistensi.

Mari kita asumsikan, bahwa aplikasi hanya mengeksekusi urutan pernyataan yang benar. Lalu apa peran DBMS, jika aplikasi itu benar?

Pertama, ternyata urutan pernyataan yang benar untuk sementara dapat mengganggu konsistensi data, dan ini - cukup aneh - adalah normal. Contoh yang usang namun dapat dipahami adalah mentransfer dana dari satu akun ke akun lainnya. Aturan konsistensi mungkin terdengar seperti ini: transfer tidak pernah mengubah jumlah total uang dalam akun (aturan semacam itu agak sulit untuk ditulis dalam SQL sebagai kendala integritas, sehingga ada di tingkat aplikasi dan tidak terlihat oleh DBMS). Transfer terdiri dari dua operasi: yang pertama mengurangi dana dalam satu akun, yang kedua - meningkatkan yang lain. Operasi pertama melanggar konsistensi data, yang kedua - mengembalikan.

Latihan yang baik adalah menerapkan aturan yang dijelaskan di atas pada tingkat kendala integritas. Apakah kamu lemah? ©

Bagaimana jika operasi pertama selesai dan yang kedua tidak? Lagi pula, itu mudah: selama operasi kedua, listrik dapat hilang, server bisa jatuh, pembagian dengan nol dapat terjadi - tetapi Anda tidak pernah tahu. Jelas bahwa konsistensi dilanggar, dan ini tidak boleh dibiarkan. Pada prinsipnya, adalah mungkin untuk menyelesaikan situasi seperti itu pada tingkat aplikasi dengan biaya upaya yang luar biasa, tetapi, untungnya, itu tidak perlu: DBMS menangani ini. Tetapi untuk ini, dia harus tahu bahwa dua operasi merupakan keseluruhan yang tak terpisahkan. Itu adalah transaksi .

Ternyata menarik: mengetahui bahwa operasi merupakan suatu transaksi, DBMS membantu menjaga konsistensi dengan menjamin keaslian transaksi, sementara tidak mengetahui apa pun tentang aturan konsistensi tertentu.

Tetapi ada titik kedua, yang lebih halus. Segera setelah beberapa transaksi simultan muncul dalam sistem yang benar-benar benar satu per satu, bersama-sama mereka dapat bekerja secara salah. Ini disebabkan oleh fakta bahwa urutan operasi dicampur: tidak dapat diasumsikan bahwa semua operasi dari satu transaksi dilakukan terlebih dahulu, dan hanya kemudian semua operasi dari yang lain.

Catatan tentang simultanitas. Memang, pada saat yang sama, transaksi dapat bekerja pada sistem dengan prosesor multi-core, dengan disk array, dll. Tetapi semua pertimbangan yang sama berlaku untuk server yang mengeksekusi perintah secara berurutan, dalam mode pembagian waktu: begitu banyak siklus, satu transaksi dieksekusi, begitu banyak siklus berbeda . Terkadang istilah eksekusi kompetitif digunakan untuk meringkas.

Situasi ketika transaksi yang benar tidak bekerja bersama dengan benar disebut anomali eksekusi simultan.

Contoh sederhana: jika suatu aplikasi ingin mendapatkan data yang benar dari database, maka paling tidak itu tidak akan melihat perubahan dalam transaksi yang tidak dikomit lainnya. Kalau tidak, Anda tidak hanya bisa mendapatkan data yang tidak konsisten, tetapi juga melihat sesuatu yang belum pernah ada dalam database (jika transaksi dibatalkan). Anomali ini disebut bacaan kotor .

Jika ada anomali lain yang lebih kompleks, yang akan kita bahas nanti.

Tentu saja, tidak mungkin untuk menolak eksekusi simultan: jika tidak, kinerja seperti apa yang bisa didiskusikan? Tetapi Anda tidak dapat bekerja dengan data yang salah.

Dan lagi, DBMS datang untuk menyelamatkan. Anda dapat membuat transaksi berjalan seolah-olah berurutan, seolah-olah satu demi satu. Dengan kata lain, terpisah satu sama lain. Pada kenyataannya, DBMS dapat melakukan operasi yang campur aduk, tetapi pada saat yang sama menjamin bahwa hasil eksekusi simultan akan bertepatan dengan hasil dari setiap eksekusi sekuensial yang mungkin. Dan ini menghilangkan kemungkinan anomali.

Jadi, kita sampai pada definisi:

Transaksi adalah serangkaian operasi yang dilakukan oleh aplikasi yang mentransfer basis data dari satu kondisi yang benar ke kondisi lain yang benar (konsistensi), asalkan transaksi tersebut selesai (atomicity) dan tanpa gangguan dari transaksi lain (isolasi).

Definisi ini menggabungkan tiga huruf pertama dari ACID akronim. Mereka begitu erat terkait satu sama lain sehingga tidak masuk akal untuk mempertimbangkan satu tanpa yang lain. Bahkan, sulit untuk merobek huruf D (daya tahan). Lagi pula, jika terjadi crash sistem, perubahan pada transaksi yang tidak dikomit tetap ada di dalamnya, yang dengannya Anda harus melakukan sesuatu untuk mengembalikan konsistensi data.

Semuanya akan baik-baik saja, tetapi implementasi isolasi lengkap adalah tugas yang sulit secara teknis, ditambah dengan penurunan throughput sistem. Oleh karena itu, dalam praktiknya, sangat sering (tidak selalu, tetapi hampir selalu) pelemahan isolasi diterapkan, yang mencegah beberapa, tetapi tidak semua, anomali. Dan ini berarti bagian dari pekerjaan untuk memastikan kebenaran data jatuh pada aplikasi. Itulah mengapa sangat penting untuk memahami tingkat isolasi apa yang digunakan dalam sistem, jaminan apa yang diberikannya dan mana yang tidak, dan bagaimana menulis kode yang benar dalam kondisi seperti itu.

Level dan anomali isolasi SQL


Standar SQL telah lama menggambarkan empat level isolasi. Level-level ini ditentukan dengan mendaftar anomali yang diperbolehkan atau tidak diizinkan saat melakukan transaksi pada level tersebut. Oleh karena itu, untuk berbicara tentang level-level ini, Anda perlu berkenalan dengan anomali.

Saya menekankan bahwa pada bagian ini kita berbicara tentang standar, yaitu, tentang teori tertentu yang menjadi dasar praktik, tetapi yang pada saat yang sama bertentangan. Karena itu, semua contoh di sini adalah spekulatif. Mereka akan menggunakan operasi yang sama pada akun pelanggan: ini cukup jelas, meskipun, diakui, itu tidak ada hubungannya dengan bagaimana operasi perbankan sebenarnya diatur.

Pembaruan yang hilang


Mari kita mulai dengan pembaruan yang hilang . Anomali ini terjadi ketika dua transaksi membaca baris yang sama dalam tabel, kemudian satu transaksi memperbarui baris ini, dan setelah itu transaksi kedua juga memperbarui baris yang sama, tidak memperhitungkan perubahan yang dilakukan oleh transaksi pertama.

Misalnya, dua transaksi akan meningkatkan jumlah pada akun yang sama sebesar 100 ₽. Transaksi pertama membaca nilai saat ini (1000 ₽), kemudian transaksi kedua membaca nilai yang sama. Transaksi pertama meningkatkan jumlah (ternyata 1100 ₽) dan menulis nilai ini. Transaksi kedua melakukan hal yang sama - mendapatkan 1.100 same yang sama dan menulisnya. Akibatnya, klien kehilangan 100 ₽.

Pembaruan yang hilang tidak diizinkan oleh standar pada tingkat isolasi apa pun.

Membaca Kotor dan Membaca Tidak Berkomitmen


Dengan bacaan kotor kita sudah bertemu di atas. Anomali ini terjadi ketika suatu transaksi membaca perubahan yang tertunda yang dibuat oleh transaksi lain.

Misalnya, transaksi pertama mentransfer semua uang dari akun klien ke akun lain, tetapi tidak mencatat perubahan. Transaksi lain membaca status akun, menerima 0 ₽ dan menolak untuk mengeluarkan uang tunai kepada klien - terlepas dari fakta bahwa transaksi pertama terganggu dan membatalkan perubahannya, sehingga nilai 0 tidak pernah ada dalam database.

Bacaan kotor diizinkan oleh standar di tingkat Baca Tidak Berkomitmen.

Komitmen Baca dan Baca Non-Ulangi


Anomali pembacaan non-berulang terjadi ketika transaksi membaca baris yang sama dua kali, dan dalam interval antara pembacaan, transaksi kedua mengubah (atau menghapus) baris ini dan melakukan perubahan. Maka transaksi pertama akan mendapatkan hasil yang berbeda.

Misalnya, biarkan aturan konsistensi melarang jumlah negatif di akun pelanggan . Transaksi pertama akan mengurangi jumlah dalam akun sebesar 100 ₽. Dia memeriksa nilai saat ini, mendapatkan 1000 ₽ dan memutuskan bahwa pengurangan dimungkinkan. Pada saat ini, transaksi kedua mengurangi jumlah dalam akun menjadi nol dan mencatat perubahan. Jika sekarang transaksi pertama memeriksa ulang jumlahnya, dia akan menerima 0 ₽ (tetapi dia sudah memutuskan untuk mengurangi nilainya, dan akun "pergi ke minus").

Pembacaan tanpa pengulangan diizinkan oleh standar pada tingkat Read Uncommitted dan Read Committed. Tapi pembacaan kotor Komitmen Baca tidak memungkinkan.

Phantom Read dan Repeatable Read


Pembacaan hantu terjadi ketika transaksi membaca satu set garis dua kali dalam kondisi yang sama, dan dalam interval antara membaca, transaksi kedua menambahkan baris yang memenuhi kondisi ini (dan melakukan perubahan). Maka transaksi pertama akan menerima set baris yang berbeda.

Misalnya, anggap aturan konsistensi melarang pelanggan memiliki lebih dari 3 akun . Transaksi pertama akan membuka akun baru, memeriksa nomor mereka saat ini (katakanlah, 2) dan memutuskan bahwa pembukaan dimungkinkan. Pada saat ini, transaksi kedua juga membuka akun baru untuk klien dan mencatat perubahan. Jika sekarang transaksi pertama memeriksa ulang jumlahnya, maka akan menerima 3 (tetapi sudah membuka akun lain dan klien memiliki 4 dari mereka).

Pembacaan hantu diizinkan oleh standar di tingkat Baca Tidak Berkomitmen, Baca Berkomitmen dan Diulang. Tetapi pada tingkat Baca Berulang, pembacaan yang tidak berulang tidak diperbolehkan.

Kurangnya Anomali dan Serializable


Standar ini mendefinisikan level lain - Serializable - di mana tidak ada anomali yang diizinkan. Dan ini sama sekali tidak sama dengan larangan pembaruan yang hilang dan pembacaan yang kotor, tidak berulang, dan hantu.

Faktanya adalah bahwa ada anomali yang secara signifikan lebih diketahui daripada yang tercantum dalam standar, dan angka yang tidak diketahui masih belum diketahui.

Serializable harus mencegah semua kelainan pada umumnya . Ini berarti bahwa pada level ini, pengembang aplikasi tidak perlu berpikir untuk menjalankan secara bersamaan. Jika transaksi melakukan urutan pernyataan yang benar, bekerja sendiri, data akan konsisten dengan operasi simultan dari transaksi ini.

Piring ringkasan


Sekarang Anda bisa membawa meja terkenal ke semua orang. Tapi di sini, untuk kejelasan, kolom terakhir ditambahkan, yang tidak ada dalam standar.
perubahan yang hilangbacaan kotorbacaan non-berulangpembacaan hantuanomali lainnya
Baca Tidak Berkomitmen-iyaiyaiyaiya
Baca berkomitmen--iyaiyaiya
Baca berulang---iyaiya
Serializable-----

Mengapa justru anomali ini?


Mengapa hanya beberapa dari banyak anomali yang mungkin ada dalam standar yang tercantum, dan mengapa ini?

Tampaknya, tidak ada yang tahu hal ini dengan pasti. Tetapi praktik di sini jelas-jelas melampaui teori, jadi mungkin saja kita tidak memikirkan anomali lain (pidato tentang standar SQL: 92).

Selain itu, diasumsikan bahwa isolasi harus dibangun di atas interlock. Gagasan protokol pemblokiran dua-fase (2PL) yang banyak digunakan adalah bahwa selama transaksi, transaksi memblokir jalur yang digunakannya, dan ketika selesai, ia melepaskan kunci. Sederhananya, semakin banyak kunci yang ditangkap transaksi, semakin baik itu diisolasi dari transaksi lainnya. Tetapi kinerja sistem lebih menderita, karena alih-alih bekerja bersama, transaksi mulai berbaris untuk jalur yang sama.

Tampak bagi saya bahwa perbedaan antara tingkat isolasi standar dijelaskan dengan tepat oleh jumlah kunci yang diperlukan.

Jika suatu transaksi memblokir baris yang diubah dari perubahan, tetapi bukan dari pembacaan, kami mendapatkan tingkat Baca Tidak Berkomitmen: perubahan yang hilang tidak diizinkan, tetapi data yang tidak terikat dapat dibaca.

Jika transaksi memblokir garis yang dapat berubah dari pembacaan dan perubahan, kami mendapatkan tingkat Komitmen Baca: Anda tidak dapat membaca data yang tidak dikomit, tetapi ketika Anda mengakses baris lagi, Anda bisa mendapatkan nilai yang berbeda (bacaan yang tidak berulang).

Jika suatu transaksi memblokir garis yang dapat dibaca dan dapat diubah dari pembacaan dan perubahan, kita mendapatkan tingkat Baca Berulang: pembacaan berulang baris akan menghasilkan nilai yang sama.

Tetapi ada masalah dengan Serializable: tidak mungkin untuk mengunci baris yang belum ada. Karena itu, kemungkinan pembacaan hantu tetap: transaksi lain dapat menambahkan (tetapi tidak menghapus) baris yang berada dalam kondisi permintaan yang dieksekusi sebelumnya, dan baris ini akan diambil kembali.

Oleh karena itu, untuk menerapkan tingkat Serializable, kunci biasa tidak cukup - Anda perlu memblokir bukan baris, tetapi kondisi (predikat). Kunci semacam itu disebut predikat . Mereka diusulkan kembali pada tahun 1976, tetapi penerapan praktis mereka dibatasi oleh kondisi yang agak sederhana, yang jelas bagaimana menggabungkan dua predikat yang berbeda. Sejauh yang saya tahu, itu belum sampai pada implementasi kunci tersebut di sistem apa pun.

Tingkat isolasi PostgreSQL


Seiring waktu, Isolasi Snapshot menggantikan protokol manajemen transaksi pemblokiran. Idenya adalah bahwa setiap transaksi bekerja dengan snapshot data yang konsisten pada titik waktu tertentu, di mana hanya perubahan yang dicatat sebelum penciptaan snapshot jatuh.

Isolasi semacam itu tidak secara otomatis memungkinkan pembacaan yang kotor. Secara formal, di PostgreSQL, Anda dapat menentukan level Read Uncommitted, tetapi akan berfungsi seperti Read Committed. Oleh karena itu, kami tidak akan berbicara tentang tingkat Baca Tidak Berkomitmen lebih lanjut.

PostgreSQL mengimplementasikan multi- versi protokol ini. Gagasan multi-versi adalah bahwa beberapa versi dari string yang sama dapat hidup berdampingan dalam DBMS. Ini memungkinkan Anda untuk membuat snapshot data menggunakan versi yang tersedia, dan bertahan dengan minimum kunci. Bahkan, hanya perubahan yang diulangi pada baris yang sama yang diblokir. Semua operasi lain dilakukan pada saat yang sama: transaksi penulisan tidak pernah memblokir transaksi membaca, dan transaksi membaca tidak pernah memblokir siapa pun.

Dengan menggunakan snapshot data, isolasi dalam PostgreSQL lebih ketat daripada standar yang disyaratkan: tingkat Baca Berulang tidak hanya memungkinkan tidak berulang, tetapi juga membaca hantu (meskipun tidak memberikan isolasi lengkap). Dan ini dicapai tanpa kehilangan efektivitas.
perubahan yang hilangbacaan kotorbacaan non-berulangpembacaan hantuanomali lainnya
Baca Tidak Berkomitmen--iyaiyaiya
Baca berkomitmen--iyaiyaiya
Baca berulang----iya
Serializable-----

Bagaimana multi-versi diimplementasikan "di bawah tenda", kita akan berbicara dalam artikel berikut, dan sekarang kita akan melihat secara terperinci pada masing-masing tiga tingkat melalui mata pengguna (seperti yang Anda tahu, yang paling menarik tersembunyi di balik "anomali lain"). Untuk melakukan ini, buat tabel akun. Alice dan Bob masing-masing memiliki $ 1.000, tetapi Bob memiliki dua akun terbuka:

=> CREATE TABLE accounts( id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, number text UNIQUE, client text, amount numeric ); => INSERT INTO accounts VALUES (1, '1001', 'alice', 1000.00), (2, '2001', 'bob', 100.00), (3, '2002', 'bob', 900.00); 

Baca berkomitmen


Kurang membaca kotor


Sangat mudah untuk memverifikasi bahwa data kotor tidak dapat dibaca. Ayo mulai transaksi. Secara default, ini akan menggunakan level isolasi Read Committed:

 => BEGIN; => SHOW transaction_isolation; 
  transaction_isolation ----------------------- read committed (1 row) 

Lebih tepatnya, level default diatur oleh parameter, itu dapat diubah jika perlu:

 => SHOW default_transaction_isolation; 
  default_transaction_isolation ------------------------------- read committed (1 row) 

Jadi, dalam transaksi terbuka, kami menarik dana dari akun, tetapi tidak mencatat perubahan. Transaksi melihat perubahannya sendiri:

 => UPDATE accounts SET amount = amount - 200 WHERE id = 1; => SELECT * FROM accounts WHERE client = 'alice'; 
  id | number | client | amount ----+--------+--------+-------- 1 | 1001 | alice | 800.00 (1 row) 

Di sesi kedua, kami memulai transaksi lain dengan tingkat Baca Komitmen yang sama. Untuk membedakan antara transaksi yang berbeda, perintah dari transaksi kedua akan diindentasi dan dicoret.

Untuk mengulangi perintah di atas (yang berguna), Anda perlu membuka dua terminal dan menjalankan psql di masing-masing. Di yang pertama, Anda dapat memasukkan perintah dari satu transaksi, dan di yang kedua - perintah dari yang lain.

 | => BEGIN; | => SELECT * FROM accounts WHERE client = 'alice'; 
 | id | number | client | amount | ----+--------+--------+--------- | 1 | 1001 | alice | 1000.00 | (1 row) 

Seperti yang diharapkan, transaksi lain tidak melihat perubahan yang tidak dikomit - pembacaan kotor tidak diperbolehkan.

Bacaan tanpa pengulangan


Sekarang biarkan transaksi pertama melakukan perubahan, dan yang kedua menjalankan kembali permintaan yang sama.

 => COMMIT; 

 | => SELECT * FROM accounts WHERE client = 'alice'; 
 | id | number | client | amount | ----+--------+--------+-------- | 1 | 1001 | alice | 800.00 | (1 row) 
 | => COMMIT; 

Permintaan sudah menerima data baru - ini adalah anomali pembacaan yang tidak berulang , yang diizinkan pada tingkat Komitmen Baca.

Kesimpulan praktis : dalam suatu transaksi tidak mungkin untuk membuat keputusan berdasarkan data yang dibaca oleh pernyataan sebelumnya - karena semuanya dapat berubah antara waktu pernyataan dieksekusi. Berikut adalah contoh yang variasinya sangat umum dalam kode aplikasi sehingga merupakan antipattern klasik:

  IF (SELECT amount FROM accounts WHERE id = 1) >= 1000 THEN UPDATE accounts SET amount = amount - 1000 WHERE id = 1; END IF; 

Selama waktu yang berlalu antara verifikasi dan pembaruan, transaksi lain dapat mengubah keadaan akun yang diinginkan, sehingga "cek" seperti itu tidak menyimpan apa pun. Lebih mudah untuk membayangkan bahwa antara operator dari satu transaksi, operator lain dari transaksi lainnya dapat "mengganjal", misalnya, seperti ini:

  IF (SELECT amount FROM accounts WHERE id = 1) >= 1000 THEN ----- | UPDATE accounts SET amount = amount - 200 WHERE id = 1; | COMMIT; ----- UPDATE accounts SET amount = amount - 1000 WHERE id = 1; END IF; 

Jika, menata ulang operator, Anda dapat merusak segalanya, maka kodenya ditulis secara tidak benar. Dan jangan menipu diri sendiri bahwa kombinasi keadaan seperti itu tidak akan terjadi - itu akan terjadi.

Bagaimana cara menulis kode dengan benar? Peluang, sebagai aturannya, berbunyi sebagai berikut:

  • Jangan menulis kode.
    Ini bukan lelucon. Misalnya, dalam kasus ini, pemeriksaan dengan mudah berubah menjadi kendala integritas:
    ALTER TABLE accounts ADD CHECK amount >= 0;
    Sekarang, tidak ada pemeriksaan yang diperlukan: itu cukup untuk hanya melakukan tindakan dan, jika perlu, menangani pengecualian yang akan muncul jika terjadi upaya untuk melanggar integritas.
  • Gunakan pernyataan SQL tunggal.
    Masalah konsistensi muncul karena fakta bahwa dalam interval antara operator transaksi lain dapat berakhir dan data yang terlihat akan berubah. Dan jika hanya ada satu operator, maka tidak ada celah.
    PostgreSQL memiliki cukup alat untuk menyelesaikan masalah yang kompleks dengan pernyataan SQL tunggal. Kami mencatat ekspresi tabel umum (CTE), di mana, antara lain, Anda dapat menggunakan pernyataan INSERT / UPDATE / DELETE, serta pernyataan INSERT ON CONFLICT, yang mengimplementasikan logika "sisipkan, dan jika sudah ada baris, perbarui" dalam satu pernyataan.
  • Kunci pengguna.
    Pilihan terakhir adalah secara manual mengatur kunci eksklusif baik pada semua baris yang diperlukan (PILIH UNTUK PEMBARUAN), atau pada seluruh tabel (LOCK TABLE). Ini selalu berhasil, tetapi meniadakan manfaat multi-versi: daripada mengeksekusi secara bersamaan, bagian dari operasi akan dilakukan secara berurutan.

Pembacaan tidak konsisten


Sebelum memulai tingkat isolasi berikutnya, kita harus mengakui bahwa tidak semuanya begitu sederhana. Implementasi PostgreSQL sedemikian rupa sehingga memungkinkan anomali lain yang kurang terkenal yang tidak diatur oleh standar.

Katakanlah transaksi pertama mulai mentransfer dana dari satu akun Bob ke yang lain:

 => BEGIN; => UPDATE accounts SET amount = amount - 100 WHERE id = 2; 

Pada saat ini, transaksi lain menghitung saldo Bob, dengan perhitungan dilakukan dalam siklus di semua akun Bob. Bahkan, transaksi dimulai dari akun pertama (dan, jelas, melihat keadaan sebelumnya):

 | => BEGIN; | => SELECT amount FROM accounts WHERE id = 2; 
 | amount | -------- | 100.00 | (1 row) 

Pada titik ini, transaksi pertama selesai dengan sukses:

 => UPDATE accounts SET amount = amount + 100 WHERE id = 3; => COMMIT; 

Dan yang lain membaca status akun kedua (dan sudah melihat nilai baru):

 | => SELECT amount FROM accounts WHERE id = 3; 
 | amount | --------- | 1000.00 | (1 row) 
 | => COMMIT; 

Dengan demikian, transaksi kedua menerima total 1100 ₽, yaitu, data yang salah. Ini adalah anomali bacaan yang tidak konsisten .

Bagaimana cara menghindari anomali seperti itu dengan tetap di Read Committed? Tentu saja, gunakan satu operator. Misalnya, seperti ini:

  SELECT sum(amount) FROM accounts WHERE client = 'bob'; 


Sejauh ini, saya berpendapat bahwa visibilitas data hanya dapat berubah di antara operator, tetapi apakah sudah jelas? Dan jika permintaan itu dieksekusi untuk waktu yang lama, dapatkah ia melihat sebagian data dalam satu keadaan, dan sebagian lainnya?

Lihat itu. Cara mudah untuk melakukan ini adalah memasukkan penundaan buatan ke dalam operator dengan memanggil fungsi pg_sleep. Parameternya menetapkan waktu tunda dalam detik.

 => SELECT amount, pg_sleep(2) FROM accounts WHERE client = 'bob'; 

Saat konstruksi ini sedang berlangsung, dalam transaksi lain, kami mentransfer dana kembali:

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100 WHERE id = 2; | => UPDATE accounts SET amount = amount - 100 WHERE id = 3; | => COMMIT; 

Hasilnya menunjukkan bahwa operator melihat data dalam keadaan di mana ia dimulai. Ini tentu saja benar.

  amount | pg_sleep ---------+---------- 0.00 | 1000.00 | (2 rows) 

Tapi ini tidak sesederhana itu. PostgreSQL memungkinkan Anda untuk mendefinisikan fungsi, sementara fungsi memiliki konsep kategori variabilitas . Jika fungsi volatil (dengan kategori VOLATILE) dipanggil dalam permintaan, dan permintaan lain dijalankan dalam fungsi ini, maka permintaan ini di dalam fungsi akan melihat data yang tidak konsisten dengan data permintaan utama.

 => CREATE FUNCTION get_amount(id integer) RETURNS numeric AS $$ SELECT amount FROM accounts a WHERE a.id = get_amount.id; $$ VOLATILE LANGUAGE sql; 

 => SELECT get_amount(id), pg_sleep(2) FROM accounts WHERE client = 'bob'; 

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100 WHERE id = 2; | => UPDATE accounts SET amount = amount - 100 WHERE id = 3; | => COMMIT; 

Dalam hal ini, kami mendapatkan data yang salah - 100 ₽ hilang:

  get_amount | pg_sleep ------------+---------- 100.00 | 800.00 | (2 rows) 

Saya menekankan bahwa efek seperti itu hanya mungkin pada tingkat isolasi yang dilakukan Read Read, dan hanya dengan kategori variabilitas VOLATILE. Masalahnya adalah tingkat isolasi dan kategori variabilitas ini digunakan secara default, jadi saya harus akui - penggaruknya terletak sangat baik. Jangan melangkah!

Pembacaan tidak konsisten dengan imbalan perubahan yang hilang


Pembacaan yang tidak konsisten dalam kerangka kerja satu operator dapat - dengan cara yang agak tak terduga - diperoleh selama pembaruan.

Mari kita lihat apa yang terjadi ketika Anda mencoba mengubah baris yang sama dengan dua transaksi. Bob sekarang memiliki 1000 ₽ pada dua akun:

 => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+-------- 2 | 2001 | bob | 200.00 3 | 2002 | bob | 800.00 (2 rows) 

Kami memulai transaksi yang mengurangi saldo Bob:

 => BEGIN; => UPDATE accounts SET amount = amount - 100 WHERE id = 3; 

Pada saat yang sama, transaksi lain menimbulkan bunga pada semua akun pelanggan dengan total saldo sama dengan atau lebih besar dari 1000 ₽:

 | => UPDATE accounts SET amount = amount * 1.01 | WHERE client IN ( | SELECT client | FROM accounts | GROUP BY client | HAVING sum(amount) >= 1000 | ); 

Melaksanakan pernyataan UPDATE terdiri dari dua bagian. Pertama, SELECT sebenarnya dieksekusi, yang memilih baris yang cocok dengan kondisi untuk memperbarui. Karena perubahan transaksi pertama tidak tetap, transaksi kedua tidak dapat melihatnya dan itu tidak mempengaruhi pilihan garis untuk menghitung bunga. Jadi, akun Bob jatuh dalam kondisi dan setelah pembaruan selesai, saldonya akan meningkat 10 ₽.

Tahap kedua eksekusi - baris yang dipilih diperbarui satu demi satu. Di sini transaksi kedua dipaksa untuk "membeku", karena baris id = 3 sudah dikunci oleh transaksi pertama.

Sementara itu, transaksi pertama melakukan perubahan:

 => COMMIT; 

Apa hasilnya?

 => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+---------- 2 | 2001 | bob | 202.0000 3 | 2002 | bob | 707.0000 (2 rows) 

Ya, di satu sisi, perintah UPDATE seharusnya tidak melihat perubahan dalam transaksi kedua. Tetapi di sisi lain, itu tidak boleh kehilangan perubahan yang dicatat dalam transaksi kedua.

Setelah kunci dilepaskan, UPDATE membaca kembali baris yang sedang berusaha diperbarui (tetapi hanya satu!). Hasilnya adalah Bob memperoleh 9 ₽, berdasarkan jumlah 900 ₽. Tetapi jika Bob memiliki 900 ₽, akunnya seharusnya tidak dimasukkan dalam sampel sama sekali.

Jadi, transaksi menerima data yang salah: beberapa baris terlihat pada satu titik waktu, beberapa di titik lainnya. Alih-alih pembaruan yang hilang, kami kembali mendapatkan anomali dalam pembacaan yang tidak konsisten .

Pembaca yang penuh perhatian mencatat bahwa dengan bantuan dari aplikasi di level Read Committed, Anda bisa mendapatkan pembaruan yang hilang. Misalnya, seperti ini:

  x := (SELECT amount FROM accounts WHERE id = 1); UPDATE accounts SET amount = x + 100 WHERE id = 1; 

Basis data tidak bisa disalahkan: ia menerima dua pernyataan SQL dan tidak tahu apa-apa bahwa nilai x + 100 entah bagaimana terkait dengan accounts.amount. Jangan menulis kode dengan cara ini.

Baca berulang


Kurangnya bacaan yang tidak berulang dan phantom


Nama tingkat isolasi itu sendiri menunjukkan bahwa bacaan tersebut dapat diulang. Kami akan memverifikasi ini, dan pada saat yang sama kami akan diyakinkan tentang tidak adanya pembacaan hantu. Untuk melakukan ini, dalam transaksi pertama, kembalikan akun Bob ke keadaan sebelumnya dan buat akun baru untuk Charlie:

 => BEGIN; => UPDATE accounts SET amount = 200.00 WHERE id = 2; => UPDATE accounts SET amount = 800.00 WHERE id = 3; => INSERT INTO accounts VALUES (4, '3001', 'charlie', 100.00); => SELECT * FROM accounts ORDER BY id; 
  id | number | client | amount ----+--------+---------+-------- 1 | 1001 | alice | 800.00 2 | 2001 | bob | 200.00 3 | 2002 | bob | 800.00 4 | 3001 | charlie | 100.00 (4 rows) 

Di sesi kedua, kami memulai transaksi dengan tingkat Baca Berulang, menunjukkannya dalam perintah BEGIN (tingkat transaksi pertama tidak penting).

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; | => SELECT * FROM accounts ORDER BY id; 
 | id | number | client | amount | ----+--------+--------+---------- | 1 | 1001 | alice | 800.00 | 2 | 2001 | bob | 202.0000 | 3 | 2002 | bob | 707.0000 | (3 rows) 

Sekarang transaksi pertama melakukan perubahan, dan yang kedua menjalankan kembali permintaan yang sama.

 => COMMIT; 

 | => SELECT * FROM accounts ORDER BY id; 
 | id | number | client | amount | ----+--------+--------+---------- | 1 | 1001 | alice | 800.00 | 2 | 2001 | bob | 202.0000 | 3 | 2002 | bob | 707.0000 | (3 rows) 
 | => COMMIT; 

Transaksi kedua terus melihat data yang sama persis seperti di awal: tidak ada perubahan pada baris yang ada maupun baris baru yang terlihat.

Di tingkat ini, Anda tidak perlu khawatir tentang sesuatu yang berubah di antara kedua operator.

Kesalahan serialisasi dengan imbalan perubahan yang hilang


Kami mengatakan di atas bahwa ketika memperbarui baris yang sama dengan dua transaksi di tingkat Komitmen Baca, mungkin terjadi anomali pembacaan yang tidak konsisten. Hal ini disebabkan oleh fakta bahwa transaksi yang tertunda membaca ulang baris yang terkunci dan dengan demikian melihatnya tidak pada titik waktu yang sama dengan sisa baris.

Pada tingkat Repeatable Read, anomali seperti itu tidak diperbolehkan, tetapi jika itu terjadi, tidak ada yang bisa dilakukan - karena itu, transaksi berakhir dengan kesalahan serialisasi. Kami memverifikasi dengan mengulangi skenario yang sama dengan persentase:

 => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+-------- 2 | 2001 | bob | 200.00 3 | 2002 | bob | 800.00 (2 rows) 
 => BEGIN; => UPDATE accounts SET amount = amount - 100.00 WHERE id = 3; 

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; | => UPDATE accounts SET amount = amount * 1.01 | WHERE client IN ( | SELECT client | FROM accounts | GROUP BY client | HAVING sum(amount) >= 1000 | ); 

 => COMMIT; 

 | ERROR: could not serialize access due to concurrent update 
 | => ROLLBACK; 

Data tetap konsisten:

 => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+-------- 2 | 2001 | bob | 200.00 3 | 2002 | bob | 700.00 (2 rows) 

Kesalahan yang sama akan terjadi dalam kasus perubahan baris kompetitif lainnya, bahkan jika kolom yang menarik bagi kami belum benar-benar berubah.

Kesimpulan praktis : jika aplikasi menggunakan tingkat isolasi Read Repeatable untuk menulis transaksi, harus disiapkan untuk mengulangi transaksi yang gagal dengan kesalahan serialisasi. Untuk transaksi read-only, hasil seperti itu tidak dimungkinkan.

Entri yang tidak konsisten


Jadi, dalam PostgreSQL, pada tingkat isolasi dari Repeatable Read, semua anomali yang dijelaskan dalam standar dicegah. Tapi tidak semuanya. Ternyata ada dua anomali yang mungkin terjadi. (Ini berlaku tidak hanya untuk PostgreSQL, tetapi juga untuk implementasi isolasi berbasis snapshot lainnya.)

Yang pertama dari anomali ini adalah catatan yang tidak konsisten .

Biarkan aturan konsistensi ini berlaku: jumlah negatif diperbolehkan di akun klien jika jumlah total pada semua akun klien ini tetap tidak negatif .

Transaksi pertama menerima jumlah dalam akun Bob: 900 ₽.

 => BEGIN ISOLATION LEVEL REPEATABLE READ; => SELECT sum(amount) FROM accounts WHERE client = 'bob'; 
  sum -------- 900.00 (1 row) 

Transaksi kedua menerima jumlah yang sama.

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; | => SELECT sum(amount) FROM accounts WHERE client = 'bob'; 
 | sum | -------- | 900.00 | (1 row) 

Transaksi pertama benar percaya bahwa jumlah salah satu akun dapat dikurangi hingga 600 ₽.

 => UPDATE accounts SET amount = amount - 600.00 WHERE id = 2; 

Dan transaksi kedua sampai pada kesimpulan yang sama. Tetapi mengurangi skor lain:

 | => UPDATE accounts SET amount = amount - 600.00 WHERE id = 3; | => COMMIT; 

 => COMMIT; => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+--------- 2 | 2001 | bob | -400.00 3 | 2002 | bob | 100.00 (2 rows) 

Kami berhasil mengurangi saldo Bob, meskipun masing-masing transaksi berfungsi dengan benar secara individual.

Anomali Hanya Baca


Ini adalah anomali kedua dan terakhir yang dimungkinkan pada tingkat Baca Berulang. Untuk mendemonstrasikannya, Anda memerlukan tiga transaksi, dua di antaranya akan mengubah data, dan yang ketiga hanya membaca.

Tapi pertama-tama, pulihkan status akun Bob:

 => UPDATE accounts SET amount = 900.00 WHERE id = 2; => SELECT * FROM accounts WHERE client = 'bob'; 
  id | number | client | amount ----+--------+--------+-------- 3 | 2002 | bob | 100.00 2 | 2001 | bob | 900.00 (2 rows) 

Transaksi pertama membebankan bunga kepada Bob pada jumlah dana di semua akun. Bunga dikreditkan ke salah satu akunnya:

 => BEGIN ISOLATION LEVEL REPEATABLE READ; -- 1 => UPDATE accounts SET amount = amount + ( SELECT sum(amount) FROM accounts WHERE client = 'bob' ) * 0.01 WHERE id = 2; 

Kemudian transaksi lain menarik uang dari akun lain Bob dan menangkap perubahannya:

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; -- 2 | => UPDATE accounts SET amount = amount - 100.00 WHERE id = 3; | => COMMIT; 

Jika pada saat ini transaksi pertama dilakukan, tidak akan ada anomali: kita dapat berasumsi bahwa transaksi pertama selesai terlebih dahulu, dan kemudian yang kedua (tetapi bukan sebaliknya, karena transaksi pertama melihat status id akun = 3 sebelum akun ini diubah dengan transaksi kedua).

Tetapi anggaplah bahwa pada saat ini transaksi ketiga (hanya baca) dimulai, yang membaca status beberapa akun yang tidak terpengaruh oleh dua transaksi pertama:

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; -- 3 | => SELECT * FROM accounts WHERE client = 'alice'; 
 | id | number | client | amount | ----+--------+--------+-------- | 1 | 1001 | alice | 800.00 | (1 row) 

Dan hanya setelah itu transaksi pertama selesai:

 => COMMIT; 

Kondisi apa yang harus dilihat oleh transaksi ketiga sekarang?

 | SELECT * FROM accounts WHERE client = 'bob'; 

Setelah dimulai, transaksi ketiga dapat melihat perubahan dalam transaksi kedua (yang sudah dilakukan), tetapi bukan yang pertama (yang belum dilakukan). Di sisi lain, kami telah menetapkan di atas bahwa transaksi kedua harus dianggap telah dimulai setelah yang pertama. Apa pun keadaan yang dilihat oleh transaksi ketiga, itu akan menjadi tidak konsisten - ini adalah anomali dari hanya transaksi pembacaan. Tetapi pada level Repeatable Read, diizinkan:

 | id | number | client | amount | ----+--------+--------+-------- | 2 | 2001 | bob | 900.00 | 3 | 2002 | bob | 0.00 | (2 rows) 
 | => COMMIT; 

Serializable


Pada tingkat Serializable, semua anomali yang mungkin dicegah. Bahkan, Serializable diimplementasikan sebagai tambahan pada isolasi berdasarkan snapshot data. Anomali yang tidak terjadi selama Baca Berulang (seperti kotor, tidak dapat diulang, baca hantu) tidak terjadi pada tingkat Serializable. Dan anomali-anomali yang muncul (pencatatan yang tidak konsisten dan anomali hanya dari transaksi pembacaan) terdeteksi dan transaksi dibatalkan - kesalahan serialisasi yang sudah dikenal tidak bisa membuat serialisasi akses terjadi.

Entri yang tidak konsisten


Sebagai ilustrasi, kami ulangi skenario dengan anomali rekaman yang tidak konsisten:

 => BEGIN ISOLATION LEVEL SERIALIZABLE; => SELECT sum(amount) FROM accounts WHERE client = 'bob'; 
  sum ---------- 910.0000 (1 row) 

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; | => SELECT sum(amount) FROM accounts WHERE client = 'bob'; 
 | sum | ---------- | 910.0000 | (1 row) 

 => UPDATE accounts SET amount = amount - 600.00 WHERE id = 2; 

 | => UPDATE accounts SET amount = amount - 600.00 WHERE id = 3; | => COMMIT; 

 => COMMIT; 
 ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt. HINT: The transaction might succeed if retried. 

Selain pada level Repeatable Read, aplikasi yang menggunakan level isolasi Serializable harus mengulangi transaksi yang berakhir dengan kesalahan serialisasi, yang juga dilaporkan kepada kami melalui petunjuk dalam pesan kesalahan.

Kami mendapatkan kesederhanaan pemrograman, tetapi harga untuk itu adalah pengelompokan paksa dari proporsi transaksi tertentu dan kebutuhan untuk mengulanginya. Seluruh pertanyaan, tentu saja, seberapa besar bagian ini. Jika hanya transaksi yang dihentikan yang benar-benar tidak kompatibel berpotongan dalam data dengan transaksi lain, semuanya akan baik. Tetapi implementasi seperti itu pasti akan berubah menjadi sumber daya intensif dan tidak efisien, karena harus melacak operasi dengan setiap baris.

Faktanya, implementasi PostgreSQL sedemikian rupa sehingga memungkinkan pemicu negatif palsu: beberapa transaksi yang benar-benar normal yang “tidak beruntung” akan terputus. Seperti yang akan kita lihat nanti, ini tergantung pada banyak alasan, misalnya, ketersediaan indeks yang sesuai atau jumlah RAM yang tersedia. Selain itu, ada beberapa pembatasan implementasi (agak serius) lainnya, misalnya, permintaan pada tingkat Serializable tidak akan berfungsi pada replika, rencana eksekusi paralel tidak akan digunakan untuk mereka. Dan meskipun upaya meningkatkan implementasi tidak berhenti, pembatasan yang ada mengurangi daya tarik tingkat isolasi ini.
Paket paralel akan muncul di PostgreSQL 12 ( patch ). Dan pertanyaan tentang replika dapat diperoleh di PostgreSQL 13 ( patch lain ).

Anomali Hanya Baca


Sehingga hanya transaksi membaca yang tidak dapat menyebabkan anomali dan tidak dapat menderita dari itu, PostgreSQL menawarkan mekanisme yang menarik: transaksi seperti itu dapat diblokir sampai pelaksanaannya aman. Ini adalah satu-satunya kasus di mana pernyataan SELECT dapat diblokir oleh pembaruan baris. Begini tampilannya:

 => UPDATE accounts SET amount = 900.00 WHERE id = 2; => UPDATE accounts SET amount = 100.00 WHERE id = 3; => SELECT * FROM accounts WHERE client = 'bob' ORDER BY id; 
  id | number | client | amount ----+--------+--------+-------- 2 | 2001 | bob | 900.00 3 | 2002 | bob | 100.00 (2 rows) 

 => BEGIN ISOLATION LEVEL SERIALIZABLE; -- 1 => UPDATE accounts SET amount = amount + ( SELECT sum(amount) FROM accounts WHERE client = 'bob' ) * 0.01 WHERE id = 2; 

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; -- 2 | => UPDATE accounts SET amount = amount - 100.00 WHERE id = 3; | => COMMIT; 

Transaksi ketiga secara eksplisit dinyatakan hanya oleh pembaca (BACA SAJA) dan ditangguhkan (DITANGGUNG JAWAB):

 | => BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE; -- 3 | => SELECT * FROM accounts WHERE client = 'alice'; 

Ketika Anda mencoba mengeksekusi permintaan, transaksi diblokir, karena jika tidak dieksekusi akan menyebabkan anomali.

 => COMMIT; 

Dan hanya setelah transaksi pertama dilakukan, yang ketiga terus mengeksekusi:

 | id | number | client | amount | ----+--------+--------+-------- | 1 | 1001 | alice | 800.00 | (1 row) 
 | => SELECT * FROM accounts WHERE client = 'bob'; 
 | id | number | client | amount | ----+--------+--------+---------- | 2 | 2001 | bob | 910.0000 | 3 | 2002 | bob | 0.00 | (2 rows) 
 | => COMMIT; 

Catatan penting lainnya: jika isolasi Serializable digunakan, maka semua transaksi dalam aplikasi harus menggunakan level ini. Anda tidak dapat mencampur transaksi Read Committed (atau Repeatable Read) dengan Serializable. Artinya, Anda dapat mencampur sesuatu, tetapi kemudian Serializable akan berperilaku seperti Baca Berulang tanpa peringatan.Mengapa ini terjadi, kami akan pertimbangkan nanti ketika kami berbicara tentang implementasi.

Jadi jika Anda memutuskan untuk menggunakan Serializble, yang terbaik adalah mengatur tingkat default secara global (meskipun ini, tentu saja, tidak melarang menentukan level yang salah secara eksplisit):

  ALTER SYSTEM SET default_transaction_isolation = 'serializable'; 

Presentasi yang lebih ketat tentang masalah-masalah yang berkaitan dengan transaksi, konsistensi dan anomali dapat ditemukan dalam buku dan kuliah kuliah Boris Asenovich Novikov , "Fundamentals of Database Technologies".

Tingkat isolasi apa yang harus saya gunakan?


Level isolasi Read Committed digunakan secara default di PostgreSQL, dan tampaknya level ini digunakan di sebagian besar aplikasi. Akan lebih mudah jika terjadi pemutusan transaksi hanya jika terjadi kegagalan, tetapi tidak untuk mencegah ketidakkonsistenan. Dengan kata lain, kesalahan serialisasi tidak dapat terjadi.

Sisi lain dari koin adalah banyaknya kemungkinan anomali yang telah dibahas secara rinci di atas. Pengembang harus selalu mengingat dan menulis kode sedemikian rupa untuk mencegah terjadinya. Jika tidak mungkin untuk merumuskan tindakan yang diperlukan dalam satu pernyataan SQL, Anda harus menggunakan kunci pengaturan secara eksplisit. Hal yang paling tidak menyenangkan adalah kode sulit untuk menguji kesalahan yang terkait dengan memperoleh data yang tidak konsisten, dan kesalahan itu sendiri dapat terjadi dengan cara yang tidak dapat diprediksi dan tidak dapat diproduksi ulang sehingga sulit untuk diperbaiki.

Tingkat isolasi dari Repeatable Read menghilangkan beberapa masalah ketidakkonsistenan, tetapi, sayangnya, tidak semua. Oleh karena itu, Anda tidak hanya harus mengingat anomali yang tersisa, tetapi juga memodifikasi aplikasi sehingga memproses kesalahan serialisasi dengan benar. Ini, tentu saja, tidak nyaman. Tetapi untuk transaksi read-only, level ini secara sempurna melengkapi Read Committed dan sangat nyaman, misalnya, untuk membuat laporan yang menggunakan beberapa query SQL.

Akhirnya, tingkat Serializable menghilangkan perlunya inkonsistensi sama sekali, membuat penulisan kode jauh lebih mudah. Satu-satunya hal yang diperlukan dari aplikasi adalah untuk dapat mengulangi transaksi ketika menerima kesalahan serialisasi. Tetapi proporsi transaksi yang terputus, overhead tambahan, dan ketidakmampuan untuk memaralelkan permintaan dapat secara signifikan mengurangi throughput sistem. Perhatikan juga bahwa level Serializable tidak berlaku pada replika, dan bahwa level tersebut tidak dapat dicampur dengan level isolasi lainnya.

Untuk dilanjutkan .

Source: https://habr.com/ru/post/id442804/


All Articles