MVCC di PostgreSQL-1. Isolasi

Halo, Habr! Dengan artikel ini saya memulai serangkaian seri (atau serangkaian set? - Singkatnya, idenya sangat muluk) tentang struktur internal PostgreSQL.

Materi akan didasarkan pada kursus pelatihan (dalam bahasa Rusia) tentang administrasi yang saya dan Pavel pluzanov ciptakan. Tidak semua orang suka menonton video (saya pasti tidak), dan membaca slide, bahkan dengan komentar, tidak baik sama sekali.

Sayangnya, satu-satunya kursus yang tersedia dalam bahasa Inggris saat ini adalah Pengantar 2 Hari untuk PostgreSQL 11 .

Tentu saja, artikelnya tidak akan persis sama dengan isi kursus. Saya hanya akan berbicara tentang bagaimana semuanya diatur, menghilangkan administrasi itu sendiri, tetapi saya akan mencoba melakukannya secara lebih rinci dan lebih menyeluruh. Dan saya percaya bahwa pengetahuan seperti ini bermanfaat bagi pengembang aplikasi seperti halnya bagi administrator.

Saya akan menargetkan mereka yang sudah memiliki pengalaman dalam menggunakan PostgreSQL dan setidaknya secara umum mengerti apa itu. Teks akan terlalu sulit untuk pemula. Sebagai contoh, saya tidak akan mengatakan sepatah kata pun tentang cara menginstal PostgreSQL dan menjalankan psql.

Hal-hal yang dipermasalahkan tidak banyak berbeda dari versi ke versi, tapi saya akan menggunakan PostgreSQL vanilla ke-11 saat ini.

Seri pertama berkaitan dengan masalah yang berkaitan dengan isolasi dan konkurensi multiversion, dan rencana seri adalah sebagai berikut:

  1. Isolasi sebagaimana dipahami oleh standar dan PostgreSQL (artikel ini).
  2. Garpu, file, halaman - apa yang terjadi di tingkat fisik.
  3. Versi baris , transaksi virtual, dan subtransaksi.
  4. Snapshots data dan visibilitas versi baris; cakrawala acara.
  5. Vakum dalam halaman dan pembaruan HOT .
  6. Vakum normal .
  7. Autovacuum .
  8. Pembungkus dan pembekuan id transaksi .

Ayo kita pergi!

Dan sebelum kita mulai, saya ingin mengucapkan terima kasih kepada Elena Indrupskaya karena menerjemahkan artikel-artikel itu ke dalam bahasa Inggris.


Apa itu isolasi dan mengapa itu penting?


Mungkin, setiap orang setidaknya menyadari keberadaan transaksi, telah menemukan singkatan ACID, dan telah mendengar tentang tingkat isolasi. Tetapi kita masih menghadapi pendapat bahwa ini berkaitan dengan teori, yang dalam praktiknya tidak perlu. Karena itu, saya akan meluangkan waktu mencoba menjelaskan mengapa ini sangat penting.

Anda tidak mungkin bahagia jika aplikasi mendapatkan data yang salah dari database atau jika aplikasi menulis data yang salah ke database.

Tetapi apakah data yang "benar"? Diketahui bahwa kendala integritas , seperti BUKAN NULL atau UNIK, dapat dibuat di tingkat basis data. Jika data selalu memenuhi batasan integritas (dan ini memang karena DBMS menjaminnya), maka mereka integral.

Apakah hal yang benar dan integral itu sama? Tidak juga. Tidak semua kendala dapat ditentukan di tingkat basis data. Beberapa kendala terlalu rumit, misalnya, yang mencakup beberapa tabel sekaligus. Dan bahkan jika suatu kendala secara umum dapat didefinisikan dalam database, tetapi untuk beberapa alasan tidak, itu tidak berarti bahwa kendala tersebut dapat dilanggar.

Jadi, kebenaran lebih kuat daripada integritas , tetapi kita tidak tahu persis apa artinya ini. Kami tidak memiliki apa-apa selain mengakui bahwa "standar emas" kebenaran adalah aplikasi yang, karena kami ingin percaya, ditulis dengan benar dan tidak pernah salah. Dalam kasus apa pun, jika suatu aplikasi tidak melanggar integritas, tetapi melanggar kebenarannya, DBMS tidak akan mengetahuinya dan tidak akan menangkap aplikasi tersebut “tidak kidal”.

Selanjutnya kita akan menggunakan istilah konsistensi untuk merujuk pada kebenaran.

Namun, mari kita asumsikan bahwa suatu aplikasi hanya menjalankan urutan operator yang benar. Apa peran DBMS jika aplikasi itu benar?

Pertama, ternyata urutan operator yang benar untuk sementara waktu dapat merusak konsistensi data, dan, anehnya, ini normal. Contoh yang usang namun jelas adalah transfer dana dari satu akun ke akun lainnya. Aturan konsistensi mungkin terdengar seperti ini: transfer tidak pernah mengubah jumlah total uang pada akun (aturan ini cukup sulit untuk ditentukan dalam SQL sebagai kendala integritas, sehingga ada di tingkat aplikasi dan tidak terlihat oleh DBMS). Transfer terdiri dari dua operasi: yang pertama mengurangi dana pada satu akun, dan yang kedua - menambahnya di yang lain. Operasi pertama memecah konsistensi data, sedangkan yang kedua mengembalikannya.

Latihan yang baik adalah menerapkan aturan di atas pada tingkat kendala integritas.

Bagaimana jika operasi pertama dilakukan dan yang kedua tidak? Bahkan, tanpa banyak basa-basi: selama operasi kedua mungkin terjadi kegagalan listrik, server crash, pembagian dengan nol - apa pun. Jelas bahwa konsistensi akan rusak, dan ini tidak dapat diizinkan. Secara umum, adalah mungkin untuk menyelesaikan masalah seperti itu di tingkat aplikasi, tetapi dengan biaya upaya yang luar biasa; namun, untungnya, itu tidak perlu: ini dilakukan oleh DBMS. Tetapi untuk melakukan ini, DBMS harus tahu bahwa kedua operasi adalah keseluruhan yang tidak dapat dibagi. Yaitu, transaksi .

Ternyata menarik: karena DBMS tahu bahwa operasi membuat transaksi, itu membantu menjaga konsistensi dengan memastikan bahwa transaksi itu atomik, dan ia melakukan ini tanpa 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 secara terpisah, mereka mungkin gagal untuk bekerja sama dengan benar. Ini karena urutan operasi digabungkan: Anda tidak dapat mengasumsikan bahwa semua operasi dari satu transaksi dilakukan terlebih dahulu, dan kemudian semua operasi dari yang lain.

Catatan tentang simultanitas. Memang, transaksi dapat berjalan secara bersamaan pada sistem dengan prosesor multi-core, disk array, dll. Tetapi alasan yang sama berlaku untuk server yang mengeksekusi perintah secara berurutan, dalam mode pembagian waktu: selama siklus jam tertentu satu transaksi dieksekusi, dan selama siklus tertentu berikutnya yang lain. Terkadang istilah eksekusi konkuren digunakan untuk generalisasi.

Situasi ketika transaksi yang benar bekerja bersama secara tidak tepat disebut anomali eksekusi bersamaan.

Untuk contoh sederhana: jika aplikasi ingin mendapatkan data yang benar dari database, itu tidak boleh, setidaknya, melihat perubahan dari transaksi yang tidak berkomitmen 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). Keanehan ini disebut pembacaan kotor .

Ada anomali lain yang lebih kompleks, yang akan kita bahas nanti.

Tentu saja tidak mungkin untuk menghindari eksekusi bersamaan: jika tidak, kinerja seperti apa yang bisa kita bicarakan? Tetapi Anda tidak dapat bekerja dengan data yang salah.

Dan lagi, DBMS datang untuk menyelamatkan. Anda dapat melakukan transaksi yang dilakukan secara berurutan, seolah-olah satu demi satu. Dengan kata lain - terisolasi satu sama lain. Pada kenyataannya, DBMS dapat melakukan operasi yang campur aduk, tetapi memastikan bahwa hasil eksekusi bersamaan akan sama dengan hasil dari beberapa eksekusi berurutan yang mungkin. Dan ini menghilangkan kemungkinan anomali.

Jadi kami 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 campur tangan dari transaksi lain (isolasi).

Definisi ini menyatukan 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 juga melepaskan huruf D (daya tahan). Memang, ketika sistem crash, ia masih memiliki perubahan transaksi yang tidak dikomit, yang dengannya Anda perlu melakukan sesuatu untuk mengembalikan konsistensi data.

Semuanya akan baik-baik saja, tetapi implementasi isolasi lengkap adalah tugas yang secara teknis sulit melibatkan pengurangan throughput sistem. Oleh karena itu, dalam praktiknya sangat sering (tidak selalu, tetapi hampir selalu) isolasi lemah digunakan, yang mencegah beberapa, tetapi tidak semua anomali. Ini berarti bahwa bagian dari pekerjaan untuk memastikan kebenaran data jatuh pada aplikasi. Untuk alasan ini, sangat penting untuk memahami tingkat isolasi yang digunakan dalam sistem, jaminan apa yang diberikannya dan apa yang tidak diberikannya, dan bagaimana menulis kode yang benar dalam kondisi seperti itu.

Tingkat isolasi dan anomali dalam standar SQL


Standar SQL telah lama menggambarkan empat tingkat isolasi. Level-level ini didefinisikan oleh daftar anomali yang diperbolehkan atau tidak diizinkan ketika transaksi dieksekusi secara bersamaan pada level ini. Oleh karena itu, untuk berbicara tentang level-level ini, perlu untuk mengetahui anomali.

Saya menekankan bahwa pada bagian ini kita berbicara tentang standar, yaitu, tentang teori, yang mendasari praktik secara signifikan, tetapi dari mana pada saat yang sama itu secara signifikan menyimpang. Karena itu, semua contoh di sini adalah spekulatif. Mereka akan menggunakan operasi yang sama pada akun pelanggan: ini cukup demonstratif, meskipun, diakui, tidak ada hubungannya dengan bagaimana operasi bank diatur dalam kenyataan.

Pembaruan kerugian


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

Misalnya, dua transaksi akan meningkatkan jumlah pada akun yang sama sebesar ₽100 (₽ adalah tanda mata uang untuk rubel Rusia). Transaksi pertama membaca nilai saat ini (₽1000) dan kemudian transaksi kedua membaca nilai yang sama. Transaksi pertama meningkatkan jumlah (ini memberi ₽1100) dan menulis nilai ini. Transaksi kedua bertindak dengan cara yang sama: ia mendapatkan ₽1100 yang sama dan menulis nilai ini. Akibatnya, pelanggan kehilangan ₽100.

Standar tidak memungkinkan pembaruan yang hilang pada tingkat isolasi apa pun.

Kotor membaca dan Baca Tidak Berkomitmen


Bacaan kotor adalah hal yang sudah kita ketahui. Anomali ini terjadi ketika suatu transaksi membaca perubahan yang belum dilakukan oleh transaksi lain.

Misalnya, transaksi pertama mentransfer semua uang dari akun pelanggan ke akun lain, tetapi tidak melakukan perubahan. Transaksi lain membaca saldo akun, untuk mendapatkan ₽0, dan menolak untuk menarik uang tunai kepada pelanggan, meskipun transaksi pertama membatalkan dan mengembalikan perubahannya, sehingga nilai 0 tidak pernah ada dalam database.

Standar memungkinkan pembacaan kotor di tingkat Baca Tidak Berkomitmen.

Baca dan Komit Baca yang tidak dapat diulang


Anomali baca yang tidak dapat diulang terjadi ketika transaksi membaca baris yang sama dua kali, dan di antara yang dibaca, transaksi kedua memodifikasi (atau menghapus) baris itu dan melakukan perubahan. Maka transaksi pertama akan mendapatkan hasil yang berbeda.

Misalnya, biarkan aturan konsistensi melarang jumlah negatif pada akun pelanggan . Transaksi pertama akan mengurangi jumlah pada akun sebesar ₽100. Ia memeriksa nilai saat ini, mendapatkan ₽1000 dan memutuskan bahwa penurunan itu mungkin. Pada saat yang sama transaksi kedua mengurangi jumlah pada akun menjadi nol dan melakukan perubahan. Jika transaksi pertama sekarang memeriksa kembali jumlahnya, itu akan mendapatkan ₽0 (tetapi telah memutuskan untuk mengurangi nilainya, dan akun “masuk ke merah”).

Standar ini memungkinkan pembacaan yang tidak dapat diulang pada tingkat Read Uncommitted dan Read Committed. Tetapi Read Committed tidak mengizinkan pembacaan kotor.

Phantom membaca dan Baca Berulang


Pembacaan hantu terjadi ketika transaksi membaca satu set baris dengan kondisi yang sama dua kali, dan di antara pembacaan, transaksi kedua menambahkan baris yang memenuhi kondisi itu (dan melakukan perubahan). Maka transaksi pertama akan mendapatkan set baris yang berbeda.

Misalnya, biarkan aturan konsistensi mencegah pelanggan memiliki lebih dari 3 akun . Transaksi pertama akan membuka akun baru, memeriksa jumlah akun saat ini (katakanlah, 2), dan memutuskan bahwa pembukaan dimungkinkan. Pada saat yang sama, transaksi kedua juga membuka akun baru untuk pelanggan dan melakukan perubahan. Sekarang jika transaksi pertama memeriksa ulang nomornya, itu akan mendapatkan 3 (tetapi sudah membuka akun lain, dan pelanggan tampaknya memiliki 4 dari mereka).

Standar ini memungkinkan phantom membaca di tingkat Baca Tidak Berkomitmen, Baca Berkomitmen, dan Baca Berulang. Namun, pembacaan yang tidak dapat diulang tidak diperbolehkan pada tingkat Baca yang Diulang.

Tidak adanya anomali dan serializable


Standar mendefinisikan satu tingkat lagi - Serializable - yang tidak memungkinkan anomali. Dan ini tidak sama dengan melarang pembaruan yang hilang dan kotor, tidak dapat diulang, atau membaca hantu.

Masalahnya adalah bahwa ada banyak anomali yang lebih dikenal daripada yang tercantum dalam standar dan juga jumlah yang tidak diketahui namun belum diketahui.

Level Serializable harus benar-benar mencegah semua anomali. Ini berarti bahwa pada level ini, pengembang aplikasi tidak perlu memikirkan eksekusi bersamaan. Jika transaksi melakukan urutan yang benar dari operator yang bekerja secara terpisah, data akan konsisten juga ketika transaksi ini dijalankan secara bersamaan.

Tabel ringkasan


Sekarang kami dapat menyediakan tabel yang terkenal. Tapi di sini kolom terakhir, yang hilang dari standar, ditambahkan untuk kejelasan.
Perubahan yang hilangKotor bacaBaca tidak dapat diulangPhantom membacaAnomali lainnya
Baca Tidak Berkomitmen-YaYaYaYa
Baca berkomitmen--YaYaYa
Baca berulang---YaYa
Serializable-----

Mengapa justru anomali ini?


Mengapa daftar standar hanya beberapa dari banyak anomali yang mungkin, dan mengapa mereka persis ini?

Sepertinya tidak ada yang tahu itu pasti. Tapi di sini praktiknya jelas di depan teori, sehingga ada kemungkinan bahwa pada waktu itu (dari standar SQL: 92), anomali lain tidak hanya dipikirkan.

Selain itu, diasumsikan bahwa isolasi harus dibangun di atas kunci. Gagasan di balik protokol Penguncian Dua Fase (2PL) yang banyak digunakan adalah bahwa selama eksekusi, transaksi mengunci baris yang digunakannya dan melepaskan kunci pada penyelesaian. Menyederhanakan, semakin banyak kunci yang diperoleh transaksi, semakin baik itu terisolasi dari transaksi lainnya. Tetapi kinerja sistem juga lebih menderita, karena alih-alih bekerja bersama, transaksi mulai mengantri untuk baris yang sama.

Perasaan saya adalah bahwa itu hanya jumlah kunci yang diperlukan, yang menyumbang perbedaan antara tingkat isolasi standar.

Jika transaksi mengunci baris yang akan diubah agar tidak diperbarui, tetapi tidak dari membaca, kami mendapatkan tingkat Baca Tidak Berkomitmen: perubahan yang hilang tidak diizinkan, tetapi data yang tidak terikat dapat dibaca.

Jika transaksi mengunci baris yang akan diubah dari pembacaan dan pembaruan, kami mendapatkan tingkat Komitmen Baca: Anda tidak dapat membaca data yang tidak dikomit, tetapi Anda bisa mendapatkan nilai yang berbeda (pembacaan yang tidak dapat diulang) ketika Anda mengakses baris lagi.

Jika transaksi mengunci baris yang akan dibaca dan dimodifikasi serta dari membaca dan memperbarui, kami mendapatkan tingkat Baca Berulang: membaca ulang baris akan mengembalikan nilai yang sama.

Tetapi ada masalah dengan Serializable: Anda tidak dapat mengunci baris yang belum ada. Oleh karena itu, pembacaan hantu masih dimungkinkan: transaksi lain mungkin menambahkan (tetapi tidak menghapus) baris yang memenuhi persyaratan permintaan yang dieksekusi sebelumnya, dan baris itu akan dimasukkan dalam pemilihan ulang.

Oleh karena itu, untuk menerapkan level Serializable, kunci normal tidak cukup - Anda perlu mengunci kondisi (predikat) daripada baris. Oleh karena itu, kunci seperti itu disebut predikat . Mereka diusulkan pada tahun 1976, tetapi penerapan praktis mereka dibatasi oleh kondisi yang cukup sederhana yang jelas bagaimana bergabung dengan dua predikat yang berbeda. Sejauh yang saya tahu, kunci tersebut belum pernah diterapkan di sistem apa pun sejauh ini.

Level isolasi dalam PostgreSQL


Seiring waktu, protokol manajemen transaksi berbasis kunci digantikan dengan protokol Isolasi Snapshot (SI). Idenya adalah bahwa setiap transaksi bekerja dengan snapshot data yang konsisten pada titik waktu tertentu, dan hanya perubahan yang masuk ke snapshot yang dilakukan sebelum dibuat.

Isolasi ini secara otomatis mencegah pembacaan kotor. Secara formal, Anda dapat menentukan level Read Uncommitted di PostgreSQL, tetapi ini akan bekerja dengan cara yang persis sama dengan Read Committed. Oleh karena itu, lebih jauh kita tidak akan berbicara tentang level Read Uncommitted sama sekali.

PostgreSQL mengimplementasikan varian multiversion dari protokol ini. Gagasan multiversion concurrency adalah bahwa beberapa versi dari baris yang sama dapat hidup berdampingan dalam DBMS. Ini memungkinkan Anda untuk membuat snapshot data menggunakan versi yang ada dan menggunakan minimum kunci. Sebenarnya, hanya perubahan berikutnya pada baris yang sama yang dikunci. Semua operasi lain dilakukan secara bersamaan: menulis transaksi tidak pernah mengunci transaksi read-only, dan transaksi read-only tidak pernah mengunci apa pun.

Dengan menggunakan snapshot data, isolasi dalam PostgreSQL lebih ketat daripada yang disyaratkan oleh standar: tingkat Baca Berulang tidak hanya memungkinkan pembacaan yang tidak dapat diulang, tetapi juga pembacaan hantu (meskipun tidak memberikan isolasi lengkap). Dan ini dicapai tanpa kehilangan efisiensi.
Perubahan yang hilangKotor bacaBaca tidak dapat diulangPhantom membacaAnomali lainnya
Baca Tidak Berkomitmen--YaYaYa
Baca berkomitmen--YaYaYa
Baca berulang----Ya
Serializable-----

Kita akan berbicara di artikel selanjutnya tentang bagaimana multiversion concurrency diimplementasikan "di bawah tenda," dan sekarang kita akan melihat secara terperinci pada masing-masing dari tiga tingkat dengan mata pengguna (seperti yang Anda tahu, yang paling menarik tersembunyi di balik "anomali lain" "). Untuk melakukan ini, mari kita buat tabel akun. Alice dan Bob masing-masing memiliki 0001000, tetapi Bob memiliki dua akun yang dibuka:

=> 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


Tidak adanya baca kotor


Sangat mudah untuk memastikan bahwa data kotor tidak dapat dibaca. Kami memulai 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, yang 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 melakukan 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 akan memulai transaksi lain dengan tingkat Komitmen Baca yang sama. Untuk membedakan antara transaksi, perintah dari transaksi kedua akan diindentasi dan ditandai dengan bilah.

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

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

Seperti yang diharapkan, transaksi lainnya tidak melihat perubahan yang tidak dikomit karena pembacaan kotor tidak diperbolehkan.

Baca tidak dapat diulang


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; 

Kueri sudah mendapatkan data baru - dan ini adalah anomali baca yang tidak dapat diulang , yang diizinkan pada tingkat Komitmen Baca.

Kesimpulan praktis : dalam suatu transaksi, Anda tidak dapat membuat keputusan berdasarkan data yang dibaca oleh operator sebelumnya karena hal-hal dapat berubah antara pelaksanaan operator. Berikut adalah contoh yang variasinya sering terjadi pada kode aplikasi sehingga dianggap sebagai 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 lewat antara memeriksa dan memperbarui, transaksi lain dapat mengubah keadaan akun dengan cara apa pun, sehingga "cek" seperti itu aman dari nol. Sangat mudah untuk membayangkan bahwa antara operator dari satu transaksi, operator lain dari transaksi lain dapat "mengganjal," misalnya, sebagai berikut:

  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 semuanya bisa rusak dengan menata ulang operator, maka kodenya ditulis secara tidak benar. Dan jangan menipu diri sendiri bahwa kebetulan seperti itu tidak akan terjadi - pasti akan terjadi.

Tetapi bagaimana cara menulis kode dengan benar? Pilihannya cenderung sebagai berikut:

  • Bukan untuk menulis kode.
    Ini bukan lelucon. Misalnya, dalam hal ini, pengecekan dengan mudah berubah menjadi kendala integritas:
    ALTER TABLE accounts ADD CHECK amount >= 0;
    Tidak diperlukan pemeriksaan sekarang: cukup lakukan operasi dan, jika perlu, tangani pengecualian yang akan terjadi jika pelanggaran integritas dilakukan.
  • Untuk menggunakan pernyataan SQL tunggal.
    Masalah konsistensi muncul karena dalam interval waktu antara operator transaksi lain dapat diselesaikan, yang akan mengubah data yang terlihat. Dan jika ada satu operator, maka tidak ada interval waktu.
    PostgreSQL memiliki teknik yang cukup untuk menyelesaikan masalah kompleks dengan satu pernyataan SQL. Mari kita perhatikan ekspresi tabel umum (CTE), di mana, di antara yang lain, Anda dapat menggunakan pernyataan INSERT / UPDATE / DELETE, serta pernyataan INSERT ON CONFLICT, yang mengimplementasikan logika "masukkan, tetapi jika baris sudah ada, perbarui ”dalam satu pernyataan.
  • Kunci khusus.
    Pilihan terakhir adalah secara manual mengatur kunci eksklusif pada semua baris yang diperlukan (PILIH UNTUK PEMBARUAN) atau bahkan pada seluruh tabel (LOCK TABLE). Ini selalu berhasil, tetapi membatalkan manfaat konkurensi multiversion: beberapa operasi akan dieksekusi secara berurutan alih-alih eksekusi bersamaan.

Baca tidak konsisten


Sebelum melanjutkan ke tingkat isolasi berikutnya, Anda harus mengakui bahwa itu tidak sesederhana kedengarannya. Implementasi PostgreSQL sedemikian rupa sehingga memungkinkan anomali lain yang kurang dikenal yang tidak diatur oleh standar.

Mari kita asumsikan bahwa transaksi pertama memulai transfer dana dari satu akun Bob ke yang lain:

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

Pada saat yang sama, transaksi lain menghitung saldo Bob, dan perhitungan dilakukan dalam satu lingkaran di atas semua akun Bob. Bahkan, transaksi dimulai dengan akun pertama (dan, jelas, melihat keadaan sebelumnya):

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

Pada titik waktu 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; 

Oleh karena itu, transaksi kedua mendapat total ₽1100, yaitu data yang salah. Dan ini adalah anomali baca yang tidak konsisten .

Bagaimana cara menghindari anomali seperti itu saat tinggal di tingkat Komitmen Baca? Tentu saja, gunakan satu operator. Sebagai contoh:

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


Sampai di sini saya menegaskan bahwa visibilitas data hanya dapat berubah di antara operator, tetapi apakah itu begitu jelas? Dan jika kueri membutuhkan waktu lama, dapatkah ia melihat bagian dari data di satu negara dan bagian di yang lain?

Mari kita periksa. Cara mudah untuk melakukan ini adalah memasukkan penundaan paksa ke operator dengan memanggil fungsi pg_sleep. Parameternya menentukan waktu tunda dalam detik.

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

Ketika operator ini dieksekusi, kami mentransfer dana kembali dalam transaksi lain:

 | => 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 yang mereka miliki pada saat eksekusi operator dimulai. Ini tidak diragukan lagi benar.

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

Tapi di sini juga tidak sesederhana itu. PostgreSQL memungkinkan Anda untuk mendefinisikan fungsi, dan fungsi memiliki konsep kategori volatilitas . Jika fungsi VOLATILE dipanggil dalam kueri dan kueri lain dieksekusi dalam fungsi itu, kueri di dalam fungsi akan melihat data yang tidak konsisten dengan data di kueri 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 ini hanya mungkin pada tingkat isolasi yang Baca Komit dan hanya dengan fungsi VOLATILE. Masalahnya adalah bahwa secara default, tepatnya tingkat isolasi ini dan kategori volatilitas ini digunakan. Jangan jatuh ke dalam perangkap!

Pembacaan tidak konsisten dengan imbalan perubahan yang hilang


Kami juga bisa mendapatkan pembacaan yang tidak konsisten dalam satu operator selama pembaruan, meskipun dengan cara yang agak tidak terduga.

Mari kita lihat apa yang terjadi ketika dua transaksi mencoba mengubah baris yang sama. Sekarang Bob 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, dalam transaksi lain, bunga timbul pada semua akun pelanggan dengan total saldo sama dengan atau lebih besar dari ,0001.000:

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

Eksekusi dari operator UPDATE terdiri dari dua bagian. Pertama, sebenarnya SELECT dijalankan, yang memilih baris untuk memperbarui yang memenuhi kondisi yang sesuai. Karena perubahan dalam transaksi pertama tidak dilakukan, transaksi kedua tidak dapat melihatnya, dan perubahan tidak mempengaruhi pemilihan baris untuk akrual bunga. Kalau begitu, akun Bob memenuhi persyaratan dan setelah pembaruan dilakukan, saldonya akan meningkat sebesar ₽10.

Tahap kedua dari eksekusi adalah memperbarui baris yang dipilih satu per satu. Di sini transaksi kedua dipaksa untuk "hang" karena baris dengan 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) 

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

Setelah kunci dilepaskan, UPDATE membaca kembali baris yang sedang berusaha diperbarui (tetapi hanya yang ini). Akibatnya, Bob bertambah ₽9, berdasarkan jumlah ₽900. Tetapi jika Bob memiliki ₽900, akunnya seharusnya tidak ada dalam pemilihan sama sekali.

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

Pembaca yang penuh perhatian mencatat bahwa dengan bantuan dari aplikasi Anda bisa mendapatkan pembaruan yang hilang bahkan pada tingkat Komitmen Baca. Sebagai contoh:

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

Basis data tidak bisa disalahkan: ia mendapat dua pernyataan SQL dan tidak tahu apa-apa tentang fakta bahwa nilai x + 100 entah bagaimana terkait dengan jumlah akun. Hindari menulis kode seperti itu.

Baca berulang


Tidak adanya non-repeatable dan phantom membaca


Nama tingkat isolasi mengasumsikan bahwa membaca dapat diulang. Mari kita periksa, dan pada saat yang sama pastikan tidak ada hantu yang dibaca. Untuk melakukan ini, dalam transaksi pertama, kami mengembalikan akun Bob ke keadaan sebelumnya dan membuat 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 dengan menetapkannya 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 mengeksekusi 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 masih melihat data yang sama persis seperti di awal: tidak ada perubahan pada baris yang ada atau baris baru yang terlihat.

Pada level ini, Anda dapat menghindari kekhawatiran tentang sesuatu yang dapat berubah di antara dua operator.

Kesalahan serialisasi dengan imbalan perubahan yang hilang


Kami telah membahas sebelumnya bahwa ketika dua transaksi memperbarui baris yang sama pada tingkat Komitmen Baca, sebuah anomali pembacaan yang tidak konsisten dapat terjadi. Ini karena transaksi menunggu membaca ulang baris yang dikunci dan karenanya tidak melihatnya sebagai titik waktu yang sama dengan baris lainnya.

Pada level Repeatable Read, anomali ini tidak diperbolehkan, tetapi jika itu terjadi, tidak ada yang bisa dilakukan - sehingga transaksi berakhir dengan kesalahan serialisasi. Mari kita periksa dengan mengulangi skenario yang sama dengan akrual bunga:

 => 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;<span/> | => UPDATE accounts SET amount = amount * 1.01<span/> | WHERE client IN (<span/> | SELECT client<span/> | FROM accounts<span/> | GROUP BY client<span/> | HAVING sum(amount) >= 1000<span/> | );<span/> 

 => 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 kompetitif lainnya dari baris, bahkan jika kolom yang menjadi perhatian kita tidak benar-benar berubah.

Kesimpulan praktis : jika aplikasi Anda menggunakan tingkat isolasi Baca Berulang untuk transaksi tulis, ia harus siap untuk mengulangi transaksi yang diakhiri dengan kesalahan serialisasi. Untuk transaksi hanya baca, hasil ini tidak dimungkinkan.

Tulisan yang tidak konsisten


Jadi, di PostgreSQL, pada tingkat isolasi Read Repeatable, semua anomali yang dijelaskan dalam standar dicegah. Namun tidak semua anomali pada umumnya. Ternyata ada dua anomali yang masih mungkin terjadi. (Ini berlaku tidak hanya untuk PostgreSQL, tetapi juga untuk implementasi lain dari Snapshot Isolasi.)

Yang pertama dari anomali ini adalah penulisan yang tidak konsisten .

Biarkan aturan konsistensi berikut berlaku: jumlah negatif pada akun pelanggan diizinkan jika jumlah total pada semua akun pelanggan tetap non-negatif .

Transaksi pertama mendapatkan jumlah pada akun Bob: ₽900.

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

Transaksi kedua mendapatkan jumlah yang sama.

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

Transaksi pertama seharusnya meyakini bahwa jumlah salah satu akun dapat dikurangi ₽600.

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

Dan transaksi kedua sampai pada kesimpulan yang sama. Tetapi mengurangi akun 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 membuat saldo Bob menjadi merah, meskipun setiap transaksi bekerja dengan benar sendirian.

Anomali transaksi hanya baca


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

Tapi pertama-tama mari kita mengembalikan keadaan 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) 

Dalam transaksi pertama, bunga pada jumlah yang tersedia di semua akun Bob timbul. 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 Bob lain dan melakukan perubahannya:

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

Jika transaksi pertama dilakukan pada saat ini, tidak ada anomali akan terjadi: kita dapat mengasumsikan bahwa transaksi pertama dieksekusi pertama dan kemudian yang kedua (tetapi bukan sebaliknya karena transaksi pertama melihat keadaan akun dengan id = 3 sebelum itu akun diubah oleh transaksi kedua).

Tetapi bayangkan bahwa pada titik 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; 

Keadaan apa yang harus dilihat oleh transaksi ketiga sekarang?

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

Setelah dimulai, transaksi ketiga dapat melihat perubahan dari transaksi kedua (yang sudah dilakukan), tetapi bukan yang pertama (yang belum dilakukan). Di sisi lain, kami telah memastikan di atas bahwa transaksi kedua harus dipertimbangkan dimulai setelah yang pertama. Status apa pun yang dilihat transaksi ketiga akan tidak konsisten - ini hanya anomali dari transaksi read-only. Tetapi pada tingkat Baca Berulang itu diizinkan:

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

Serializable


Level Serializable mencegah semua anomali yang mungkin terjadi. Bahkan, Serializable dibangun di atas Snapshot Isolasi. Anomali yang tidak terjadi dengan Baca Berulang (seperti kotor, tidak dapat diulang, atau membaca hantu) juga tidak terjadi pada tingkat Serializable. Dan anomali yang terjadi (penulisan yang tidak konsisten dan anomali transaksi read-only) terdeteksi, dan transaksi dibatalkan - kesalahan serialisasi yang dikenal terjadi: tidak dapat membuat serial akses .

Tulisan yang tidak konsisten


Untuk menggambarkan ini, mari kita ulangi skenario dengan anomali tulis 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. 

Seperti halnya pada tingkat Baca Berulang, aplikasi yang menggunakan tingkat isolasi Serializable harus mengulangi transaksi yang diakhiri dengan kesalahan serialisasi, seperti yang diminta oleh pesan kesalahan tersebut.

Kami mendapatkan kesederhanaan pemrograman, tetapi harga untuk itu adalah pemutusan paksa sebagian transaksi dan kebutuhan untuk mengulanginya. Pertanyaannya, tentu saja, seberapa besar fraksi ini. Jika hanya transaksi yang diakhiri yang tumpang tindih dengan transaksi lain, akan lebih baik. Tetapi implementasi seperti itu mau tidak mau akan menjadi sumber daya intensif dan tidak efisien karena Anda harus melacak operasi di setiap baris.

Sebenarnya, implementasi PostgreSQL sedemikian rupa sehingga memungkinkan negatif palsu: beberapa transaksi yang benar-benar normal yang hanya "sial" juga akan dibatalkan. Seperti yang akan kita lihat nanti, ini tergantung pada banyak faktor, seperti ketersediaan indeks yang sesuai atau jumlah RAM yang tersedia. Selain itu, ada beberapa batasan implementasi lain (cukup parah), misalnya, kueri di tingkat Serializable tidak akan berfungsi pada replika, dan mereka tidak akan menggunakan rencana eksekusi paralel. Meskipun pekerjaan untuk meningkatkan implementasi terus berlanjut, keterbatasan yang ada membuat tingkat isolasi ini kurang menarik.
Paket paralel akan muncul pada PostgreSQL 12 ( patch ). Dan pertanyaan tentang replika dapat mulai bekerja di PostgreSQL 13 ( tambalan lain ).

Anomali transaksi hanya baca


Untuk transaksi read-only untuk tidak menghasilkan anomali dan tidak menderita, PostgreSQL menawarkan teknik yang menarik: transaksi semacam itu dapat dikunci hingga pelaksanaannya aman. Ini adalah satu-satunya kasus ketika operator SELECT dapat dikunci oleh pembaruan baris. Seperti inilah bentuknya:

 => 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 BACA HANYA dan DITANGGUHKAN:

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

Saat mencoba mengeksekusi kueri, transaksi dikunci karena jika tidak maka akan menyebabkan anomali.

 => COMMIT; 

Dan hanya setelah transaksi pertama dilakukan, yang ketiga melanjutkan eksekusi:

 | 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, semua transaksi dalam aplikasi harus menggunakan level ini. Anda tidak dapat mencampur transaksi Read-Committed (atau Repeatable Read) dengan Serializable. Artinya, Anda dapat mencampur, tetapi kemudian Serializable akan berperilaku seperti Baca Berulang tanpa peringatan. Kami akan membahas mengapa ini terjadi 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 akan mencegah Anda menentukan tingkat yang salah secara eksplisit):

 ALTER SYSTEM SET default_transaction_isolation = 'serializable'; 

Anda dapat menemukan presentasi yang lebih teliti tentang masalah-masalah yang berkaitan dengan transaksi, konsistensi dan anomali dalam buku dan kuliah oleh Boris Novikov "Dasar-dasar teknologi basis data" (hanya tersedia di Russion).

Tingkat isolasi apa yang digunakan?


Level isolasi Read Committed digunakan secara default di PostgreSQL, dan kemungkinan level ini digunakan di sebagian besar aplikasi. Default ini nyaman karena pada level ini transaksi dibatalkan hanya mungkin jika terjadi kegagalan, tetapi bukan sebagai sarana untuk mencegah inkonsistensi. Dengan kata lain, kesalahan serialisasi tidak dapat terjadi.

Sisi lain dari koin adalah sejumlah besar kemungkinan anomali, yang telah dibahas secara rinci di atas. Insinyur perangkat lunak selalu harus mengingatnya dan menulis kode agar tidak membiarkan mereka muncul. Jika Anda tidak dapat membuat kode tindakan yang diperlukan dalam satu pernyataan SQL, Anda harus menggunakan penguncian eksplisit. Yang paling menyusahkan adalah kode sulit untuk menguji kesalahan yang terkait dengan mendapatkan data yang tidak konsisten, dan kesalahan itu sendiri dapat terjadi dengan cara yang tidak dapat diprediksi dan tidak dapat direproduksi dan karenanya sulit untuk diperbaiki.

Tingkat isolasi Baca Berulang menghilangkan beberapa masalah inkonsistensi, tetapi sayangnya, tidak semua. Oleh karena itu, Anda tidak hanya harus ingat tentang anomali yang tersisa, tetapi juga memodifikasi aplikasi sehingga benar menangani kesalahan serialisasi. Ini tentu saja merepotkan. Tetapi untuk transaksi read-only, level ini secara sempurna melengkapi Read Committed dan sangat nyaman, misalnya, untuk membuat laporan yang menggunakan beberapa kueri SQL.

Akhirnya, tingkat Serializable memungkinkan Anda untuk tidak khawatir tentang inkonsistensi sama sekali, yang sangat memudahkan pengkodean. Satu-satunya hal yang diperlukan aplikasi adalah untuk dapat mengulangi transaksi apa pun ketika mendapatkan kesalahan serialisasi. Tetapi fraksi transaksi yang dibatalkan, overhead tambahan, dan ketidakmampuan untuk memaralelkan kueri 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.

Baca terus .

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


All Articles