5.1 Isu keserentakan

Mari kita mulakan dengan teori yang agak jauh.

Mana-mana sistem maklumat (atau ringkasnya, aplikasi) yang dibuat oleh pengaturcara terdiri daripada beberapa blok biasa, setiap satunya menyediakan sebahagian daripada fungsi yang diperlukan. Sebagai contoh, cache digunakan untuk mengingati hasil operasi intensif sumber untuk memastikan pembacaan data yang lebih pantas oleh pelanggan, alat pemprosesan strim membolehkan anda menghantar mesej kepada komponen lain untuk pemprosesan tak segerak dan alat pemprosesan kelompok digunakan untuk " rake" volum terkumpul data dengan beberapa keteraturan. .

Dan dalam hampir setiap aplikasi, pangkalan data (DB) terlibat dalam satu cara atau yang lain, yang biasanya melaksanakan dua fungsi: menyimpan data apabila diterima daripada anda dan kemudian memberikannya kepada anda atas permintaan. Jarang sekali sesiapa terfikir untuk mencipta pangkalan data mereka sendiri, kerana sudah banyak penyelesaian siap sedia. Tetapi bagaimana anda memilih yang sesuai untuk permohonan anda?

Jadi, mari kita bayangkan bahawa anda telah menulis aplikasi dengan antara muka mudah alih yang membolehkan anda memuatkan senarai tugasan yang disimpan sebelum ini di sekeliling rumah - iaitu, baca daripada pangkalan data, dan tambahkannya dengan tugasan baharu, serta mengutamakan setiap perkara yang spesifik. tugasan - dari 1 (tertinggi) hingga 3 (terendah). Katakan aplikasi mudah alih anda digunakan oleh seorang sahaja pada satu masa. Tetapi kini anda berani memberitahu ibu anda tentang ciptaan anda, dan kini dia telah menjadi pengguna tetap kedua. Apakah yang berlaku jika anda membuat keputusan pada masa yang sama, tepat dalam milisaat yang sama, untuk menetapkan beberapa tugas - "cuci tingkap" - ke tahap keutamaan yang berbeza?

Dari segi profesional, pertanyaan pangkalan data anda dan ibu boleh dianggap sebagai 2 proses yang membuat pertanyaan kepada pangkalan data. Proses ialah entiti dalam atur cara komputer yang boleh dijalankan pada satu atau lebih benang. Biasanya, proses mempunyai imej kod mesin, memori, konteks dan sumber lain. Dengan kata lain, proses itu boleh dicirikan sebagai pelaksanaan arahan program pada pemproses. Apabila aplikasi anda membuat permintaan kepada pangkalan data, kami bercakap tentang fakta bahawa pangkalan data anda memproses permintaan yang diterima melalui rangkaian daripada satu proses. Jika terdapat dua pengguna duduk dalam aplikasi pada masa yang sama, maka boleh terdapat dua proses pada bila-bila masa tertentu.

Apabila beberapa proses membuat permintaan kepada pangkalan data, ia mendapati ia dalam keadaan tertentu. Sistem stateful adalah sistem yang mengingati peristiwa sebelumnya dan menyimpan beberapa maklumat, yang dipanggil "keadaan". Pembolehubah yang diisytiharkan sebagai integerboleh mempunyai keadaan 0, 1, 2, atau katakan 42. Mutex (pengecualian bersama) mempunyai dua keadaan: dikunci atau dibuka kunci , sama seperti semafor binari ("diperlukan" lwn. "dilepaskan") dan secara amnya binari (perduaan) jenis data dan pembolehubah yang boleh mempunyai hanya dua keadaan - 1 atau 0.

Berdasarkan konsep keadaan, beberapa struktur matematik dan kejuruteraan berasaskan, seperti automaton terhingga - model yang mempunyai satu input dan satu output dan berada dalam satu set keadaan terhingga pada setiap saat masa - dan "keadaan ” corak reka bentuk, di mana objek mengubah tingkah laku bergantung pada keadaan dalaman (contohnya, bergantung pada nilai yang diberikan kepada satu atau pembolehubah lain).

Jadi, kebanyakan objek dalam dunia mesin mempunyai beberapa keadaan yang boleh berubah dari semasa ke semasa: saluran paip kami, yang memproses paket data yang besar, melemparkan ralat dan menjadi gagal , atau harta objek Wallet, yang menyimpan jumlah wang yang tinggal dalam pengguna akaun, perubahan selepas resit gaji.

Peralihan (“peralihan”) dari satu keadaan ke keadaan lain—katakan, daripada sedang berjalan kepada gagal —dipanggil operasi. Mungkin, semua orang tahu operasi CRUD - create, read, update, deleteatau kaedah HTTP yang serupa - POST, GET, PUT, DELETE. Tetapi pengaturcara sering memberikan nama lain untuk operasi dalam kod mereka, kerana operasi boleh menjadi lebih kompleks daripada hanya membaca nilai tertentu dari pangkalan data - ia juga boleh menyemak data, dan kemudian operasi kami, yang telah mengambil bentuk fungsi, akan dipanggil, sebagai contoh, Dan validate()siapa yang melaksanakan fungsi-operasi ini? proses yang telah diterangkan.

Sedikit lagi, dan anda akan faham mengapa saya menerangkan istilah dengan terperinci!

Mana-mana operasi - sama ada fungsi, atau, dalam sistem yang diedarkan, menghantar permintaan ke pelayan lain - mempunyai 2 sifat: masa invokasi dan masa siap (masa penyiapan) , yang akan lebih besar daripada masa invokasi (penyelidik dari Jepsen teruskan daripada andaian teori bahawa kedua-dua cap masa ini akan diberikan jam khayalan, disegerakkan sepenuhnya, tersedia secara global).

Mari bayangkan aplikasi senarai tugasan kami. Anda membuat permintaan kepada pangkalan data melalui antara muka mudah alih dalam 14:00:00.014, dan ibu anda dalam 13:59:59.678(iaitu, 336 milisaat sebelum ini) mengemas kini senarai tugasan melalui antara muka yang sama, menambah mencuci pinggan padanya. Dengan mengambil kira kelewatan rangkaian dan kemungkinan barisan tugas untuk pangkalan data anda, jika, sebagai tambahan kepada anda dan ibu anda, semua rakan ibu anda juga menggunakan aplikasi anda, pangkalan data boleh melaksanakan permintaan ibu selepas ia memproses permohonan anda. Dalam erti kata lain, terdapat kemungkinan bahawa dua permintaan anda, serta permintaan daripada teman wanita ibu anda, akan dihantar ke data yang sama pada masa yang sama (secara serentak).

Oleh itu, kami telah sampai kepada istilah yang paling penting dalam bidang pangkalan data dan aplikasi yang diedarkan - concurrency. Apakah sebenarnya yang boleh bermakna serentak dua operasi? Jika beberapa operasi T1 dan beberapa operasi T2 diberikan, maka:

  • T1 boleh dimulakan sebelum masa mula pelaksanaan T2, dan tamat antara masa mula dan tamat T2
  • T2 boleh dimulakan sebelum masa mula T1, dan tamat antara permulaan dan tamat T1
  • T1 boleh dimulakan dan diselesaikan antara masa mula dan tamat pelaksanaan T1
  • dan mana-mana senario lain di mana T1 dan T2 mempunyai masa pelaksanaan biasa

Adalah jelas bahawa dalam rangka kerja kuliah ini, kita bercakap terutamanya tentang pertanyaan yang memasuki pangkalan data dan bagaimana sistem pengurusan pangkalan data melihat pertanyaan ini, tetapi istilah concurrency adalah penting, contohnya, dalam konteks sistem pengendalian. Saya tidak akan menyimpang terlalu jauh daripada topik artikel ini, tetapi saya fikir adalah penting untuk menyatakan bahawa konkurensi yang kita bincangkan di sini tidak berkaitan dengan dilema konkurensi dan konkurensi serta perbezaannya, yang dibincangkan dalam konteks sistem pengendalian dan pengkomputeran berprestasi tinggi. Keselarian ialah satu cara untuk mencapai keselarasan dalam persekitaran dengan berbilang teras, pemproses atau komputer. Kami bercakap tentang konkurensi dalam erti kata akses serentak proses yang berbeza kepada data biasa.

Dan apa, sebenarnya, boleh salah, secara teori semata-mata?

Apabila mengusahakan data yang dikongsi, banyak masalah yang berkaitan dengan konkurensi, juga dipanggil "keadaan perlumbaan", boleh berlaku. Masalah pertama berlaku apabila proses menerima data yang tidak sepatutnya diterima: data tidak lengkap, sementara, dibatalkan atau sebaliknya "salah". Masalah kedua ialah apabila proses menerima data basi, iaitu data yang tidak sepadan dengan keadaan terakhir pangkalan data yang disimpan. Katakan sesetengah aplikasi telah mengeluarkan wang daripada akaun pengguna dengan baki sifar, kerana pangkalan data mengembalikan status akaun kepada aplikasi, tidak mengambil kira pengeluaran terakhir wang daripadanya, yang berlaku hanya beberapa milisaat yang lalu. Keadaannya begitu-begitu, bukan?

5.2 Transaksi Datang untuk Menyelamatkan Kami

Untuk menyelesaikan masalah sedemikian, konsep transaksi muncul - sekumpulan operasi berurutan tertentu (perubahan keadaan) dengan pangkalan data, yang merupakan operasi tunggal secara logik. Saya akan memberikan contoh dengan bank sekali lagi - dan bukan secara kebetulan, kerana konsep transaksi muncul, nampaknya, tepat dalam konteks bekerja dengan wang. Contoh klasik urus niaga ialah pemindahan wang dari satu akaun bank ke akaun bank yang lain: anda perlu terlebih dahulu mengeluarkan amaun daripada akaun sumber dan kemudian mendepositkannya ke dalam akaun sasaran.

Untuk urus niaga ini dijalankan, aplikasi perlu melakukan beberapa tindakan dalam pangkalan data: menyemak baki penghantar, menyekat amaun pada akaun penghantar, menambah amaun ke akaun penerima dan menolak amaun daripada pengirim. Terdapat beberapa keperluan untuk transaksi sedemikian. Sebagai contoh, permohonan tidak boleh menerima maklumat lapuk atau tidak betul tentang baki - contohnya, jika pada masa yang sama transaksi selari berakhir dengan ralat separuh jalan, dan dana tidak didebitkan daripada akaun - dan permohonan kami telah menerima maklumat bahawa dana telah dihapus kira.

Untuk menyelesaikan masalah ini, sifat transaksi seperti "pengasingan" dipanggil: urus niaga kami dilaksanakan seolah-olah tiada urus niaga lain dilakukan pada masa yang sama. Pangkalan data kami menjalankan operasi serentak seolah-olah ia melaksanakannya satu demi satu, secara berurutan - sebenarnya, tahap pengasingan tertinggi dipanggil Strict Serializable . Ya, yang tertinggi, yang bermaksud bahawa terdapat beberapa peringkat.

"Berhenti," anda berkata. Pegang kuda anda, tuan.

Mari kita ingat bagaimana saya menerangkan bahawa setiap operasi mempunyai masa panggilan dan masa pelaksanaan. Untuk kemudahan, anda boleh mempertimbangkan untuk memanggil dan melaksanakan sebagai 2 tindakan. Kemudian senarai diisih semua panggilan dan tindakan pelaksanaan boleh dipanggil sejarah pangkalan data. Kemudian tahap pengasingan transaksi ialah satu set sejarah. Kami menggunakan tahap pengasingan untuk menentukan cerita yang "baik". Apabila kami mengatakan bahawa cerita "memecahkan kebolehbolehan bersiri" atau "tidak boleh bersiri", kami maksudkan bahawa cerita itu bukan dalam set cerita boleh bersiri.

Untuk menjelaskan jenis cerita yang kita bicarakan, saya akan memberikan contoh. Sebagai contoh, terdapat sejarah sedemikian - bacaan pertengahan . Ia berlaku apabila transaksi A dibenarkan membaca data daripada baris yang telah diubah suai oleh transaksi B lain yang sedang berjalan dan belum lagi dilakukan ("tidak dilakukan") - iaitu, sebenarnya, perubahan itu belum lagi dilakukan oleh transaksi B, dan ia boleh membatalkannya pada bila-bila masa. Dan, sebagai contoh, bacaan yang dibatalkan hanyalah contoh kami dengan transaksi pengeluaran yang dibatalkan

Terdapat beberapa kemungkinan anomali. Iaitu, anomali adalah sejenis keadaan data yang tidak diingini yang boleh berlaku semasa capaian kompetitif kepada pangkalan data. Dan untuk mengelakkan keadaan tertentu yang tidak diingini, pangkalan data menggunakan tahap pengasingan yang berbeza - iaitu tahap perlindungan data yang berbeza daripada keadaan yang tidak diingini. Tahap ini (4 keping) disenaraikan dalam standard ANSI SQL-92.

Penerangan tahap ini nampaknya samar-samar bagi sesetengah penyelidik, dan mereka menawarkan klasifikasi mereka sendiri yang lebih terperinci. Saya menasihati anda untuk memberi perhatian kepada Jepsen yang telah disebutkan, serta projek Hermitage, yang bertujuan untuk menjelaskan dengan tepat tahap pengasingan yang ditawarkan oleh DBMS tertentu, seperti MySQL atau PostgreSQL. Jika anda membuka fail daripada repositori ini, anda boleh melihat urutan perintah SQL yang mereka gunakan untuk menguji pangkalan data untuk anomali tertentu, dan anda boleh melakukan sesuatu yang serupa untuk pangkalan data yang anda minati). Berikut ialah satu contoh daripada repositori untuk memastikan anda berminat:

-- Database: MySQL

-- Setup before test
create table test (id int primary key, value int) engine=innodb;
insert into test (id, value) values (1, 10), (2, 20);

-- Test the "read uncommited" isolation level on the "Intermediate Reads" (G1b) anomaly
set session transaction isolation level read uncommitted; begin; -- T1
set session transaction isolation level read uncommitted; begin; -- T2
update test set value = 101 where id = 1; -- T1
select * from test; -- T2. Shows 1 => 101
update test set value = 11 where id = 1; -- T1
commit; -- T1
select * from test; -- T2. Now shows 1 => 11
commit; -- T2

-- Result: doesn't prevent G1b

Adalah penting untuk memahami bahawa untuk pangkalan data yang sama, sebagai peraturan, anda boleh memilih salah satu daripada beberapa jenis pengasingan. Mengapa tidak memilih penebat terkuat? Kerana, seperti segala-galanya dalam sains komputer, tahap pengasingan yang dipilih harus sepadan dengan pertukaran yang kami sedia lakukan - dalam kes ini, pertukaran dalam kelajuan pelaksanaan: semakin kuat tahap pengasingan, semakin perlahan permintaan akan telah di proses. Untuk memahami tahap pengasingan yang anda perlukan, anda perlu memahami keperluan untuk aplikasi anda, dan untuk memahami sama ada pangkalan data yang anda pilih menawarkan tahap ini, anda perlu melihat dokumentasi - untuk kebanyakan aplikasi ini sudah mencukupi, tetapi jika anda mempunyai beberapa keperluan yang sangat ketat, adalah lebih baik untuk mengatur ujian seperti yang dilakukan oleh lelaki dari projek Hermitage.

5.3 "I" dan huruf lain dalam ACID

Pengasingan pada asasnya ialah apa yang orang maksudkan apabila mereka bercakap tentang ACID secara umum. Dan atas sebab inilah saya memulakan analisis akronim ini dengan pengasingan, dan tidak mengikut urutan, seperti yang biasa dilakukan oleh mereka yang cuba menerangkan konsep ini. Sekarang mari kita lihat baki tiga huruf.

Ingat sekali lagi contoh kami dengan pindahan wang melalui bank. Transaksi untuk memindahkan dana dari satu akaun ke akaun yang lain termasuk operasi pengeluaran dari akaun pertama dan operasi penambahan pada yang kedua. Jika operasi pengisian semula akaun kedua gagal, anda mungkin tidak mahu operasi pengeluaran dari akaun pertama berlaku. Dalam erti kata lain, sama ada transaksi itu berjaya sepenuhnya, atau ia tidak berlaku sama sekali, tetapi ia tidak boleh dibuat hanya untuk beberapa bahagian. Sifat ini dipanggil "atomicity", dan ia adalah "A" dalam ACID.

Apabila transaksi kami dilaksanakan, maka, seperti mana-mana operasi, ia memindahkan pangkalan data dari satu keadaan sah ke keadaan yang lain. Sesetengah pangkalan data menawarkan apa yang dipanggil kekangan - iaitu, peraturan yang digunakan pada data yang disimpan, contohnya, mengenai kunci primer atau sekunder, indeks, nilai lalai, jenis lajur, dsb. Jadi, apabila membuat transaksi, kita mesti yakin bahawa semua kekangan ini akan dipenuhi.

Jaminan ini dipanggil "konsistensi" dan surat Cdalam ACID (jangan dikelirukan dengan konsistensi dari dunia aplikasi yang diedarkan, yang akan kita bincangkan kemudian). Saya akan memberikan contoh yang jelas untuk konsistensi dalam erti kata ACID: permohonan untuk kedai dalam talian ingin menambah ordersbaris pada jadual, dan ID daripada jadual product_idakan ditunjukkan dalam lajur - tipikal .productsforeign key

Jika produk, katakan, telah dialih keluar dari pelbagai, dan, dengan itu, dari pangkalan data, maka operasi sisipan baris tidak sepatutnya berlaku, dan kami akan mendapat ralat. Jaminan ini, berbanding yang lain, agak jauh, pada pendapat saya - jika hanya kerana penggunaan aktif kekangan dari pangkalan data bermakna mengalihkan tanggungjawab untuk data (serta peralihan sebahagian logik perniagaan, jika kita bercakap tentang kekangan seperti CHECK ) dari aplikasi ke pangkalan data, yang, seperti yang mereka katakan sekarang, adalah begitu.

Dan akhirnya, ia kekal D- "rintangan" (ketahanan). Kegagalan sistem atau sebarang kegagalan lain tidak seharusnya menyebabkan kehilangan keputusan transaksi atau kandungan pangkalan data. Iaitu, jika pangkalan data menjawab bahawa transaksi itu berjaya, maka ini bermakna data itu direkodkan dalam memori tidak meruap - contohnya, pada cakera keras. Ini, dengan cara ini, tidak bermakna anda akan segera melihat data pada permintaan baca seterusnya.

Pada hari yang lain, saya bekerja dengan DynamoDB dari AWS (Amazon Web Services), dan menghantar beberapa data untuk disimpan, dan selepas menerima jawapan HTTP 200(OK), atau sesuatu seperti itu, saya memutuskan untuk menyemaknya - dan tidak melihat ini data dalam pangkalan data untuk 10 Saat seterusnya. Iaitu, DynamoDB menyerahkan data saya, tetapi tidak semua nod disegerakkan serta-merta untuk mendapatkan salinan data terkini (walaupun ia mungkin berada dalam cache). Di sini kita sekali lagi memanjat ke wilayah konsistensi dalam konteks sistem yang diedarkan, tetapi masa untuk membincangkannya masih belum tiba.

Jadi sekarang kita tahu apa itu jaminan ACID. Dan kita juga tahu mengapa ia berguna. Tetapi adakah kita benar-benar memerlukannya dalam setiap permohonan? Dan jika tidak, bilakah sebenarnya? Adakah semua DB menawarkan jaminan ini, dan jika tidak, apakah yang mereka tawarkan?