MySQL: Tabel Transaksi vs Penguncian

110

Saya agak bingung dengan transaksi vs penguncian tabel untuk memastikan integritas database dan memastikan SELECT dan UPDATE tetap sinkron dan tidak ada koneksi lain yang mengganggu. Aku ingin:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Saya perlu memastikan bahwa tidak ada kueri lain yang akan mengganggu dan melakukan hal yang sama SELECT(membaca 'nilai lama' sebelum sambungan tersebut selesai memperbarui baris.

Saya tahu saya dapat default untuk LOCK TABLES tablehanya memastikan bahwa hanya 1 koneksi yang melakukan ini pada satu waktu, dan membuka kuncinya ketika saya selesai, tetapi itu tampak seperti berlebihan. Akankah membungkus itu dalam transaksi melakukan hal yang sama (memastikan tidak ada koneksi lain yang mencoba proses yang sama sementara yang lain masih memproses)? Atau akankah SELECT ... FOR UPDATEatau SELECT ... LOCK IN SHARE MODElebih baik?

Ryan
sumber

Jawaban:

173

Mengunci tabel mencegah pengguna DB lain mempengaruhi baris / tabel yang telah Anda kunci. Tapi kunci, di dalam dan dari dirinya sendiri, TIDAK akan memastikan bahwa logika Anda keluar dalam keadaan yang konsisten.

Pikirkan sistem perbankan. Saat Anda membayar tagihan secara online, setidaknya ada dua akun yang terpengaruh oleh transaksi tersebut: Akun Anda, tempat uang diambil. Dan akun penerima, tempat uang ditransfer. Dan rekening bank, tempat mereka akan dengan senang hati menyetorkan semua biaya layanan yang dibebankan pada transaksi tersebut. Mengingat (seperti yang diketahui semua orang akhir-akhir ini) bahwa bank sangat bodoh, katakanlah sistem mereka bekerja seperti ini:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Sekarang, tanpa kunci dan tanpa transaksi, sistem ini rentan terhadap berbagai kondisi balapan, yang terbesar adalah beberapa pembayaran yang dilakukan di akun Anda, atau akun penerima secara paralel. Sementara kode Anda telah mengambil saldo Anda dan melakukan huge_overdraft_fees () dan yang lainnya, sangat mungkin bahwa beberapa pembayaran lain akan menjalankan jenis kode yang sama secara paralel. Mereka akan mengambil saldo Anda (katakanlah, $ 100), melakukan transaksi mereka (ambil $ 20 yang Anda bayarkan, dan $ 30 yang mereka gunakan untuk mengacaukan Anda), dan sekarang kedua jalur kode memiliki dua saldo berbeda: $ 80 dan $ 70. Bergantung pada saldo mana yang terakhir kali berakhir, Anda akan mendapatkan salah satu dari dua saldo tersebut di akun Anda, bukan $ 50 yang seharusnya Anda dapatkan ($ 100 - $ 20 - $ 30). Dalam kasus ini, "kesalahan bank dalam keuntungan Anda"

Sekarang, katakanlah Anda menggunakan kunci. Pembayaran tagihan Anda ($ 20) menyentuh pipa terlebih dahulu, sehingga menang dan mengunci catatan akun Anda. Sekarang Anda memiliki penggunaan eksklusif, dan dapat mengurangi $ 20 dari saldo, dan menulis kembali saldo baru dengan damai ... dan akun Anda berakhir dengan $ 80 seperti yang diharapkan. Tapi ... uhoh ... Anda mencoba memperbarui akun penerima, dan akun itu terkunci, dan dikunci lebih lama dari yang diizinkan oleh kode, waktu transaksi Anda habis ... Kami berurusan dengan bank-bank bodoh, jadi alih-alih mengalami kesalahan yang tepat penanganan, kode hanya menarik exit(), dan $ 20 Anda lenyap menjadi embusan elektron. Sekarang Anda kehabisan $ 20, dan Anda masih berhutang $ 20 ke receiver, dan telepon Anda diambil alih.

Jadi ... masukkan transaksi. Anda memulai transaksi, Anda mendebit akun Anda $ 20, Anda mencoba mengkredit penerima dengan $ 20 ... dan sesuatu meledak lagi. Tapi kali ini, alih-alih exit(), kodenya bisa begitu saja rollback, dan poof, $ 20 Anda secara ajaib ditambahkan kembali ke akun Anda.

Pada akhirnya, intinya adalah:

Kunci mencegah orang lain mengganggu catatan database apa pun yang Anda tangani. Transaksi mencegah kesalahan "nanti" mengganggu hal "sebelumnya" yang telah Anda lakukan. Tidak satu pun yang dapat menjamin bahwa semuanya berjalan dengan baik pada akhirnya. Tapi bersama-sama, mereka melakukannya.

dalam pelajaran besok: The Joy of Deadlocks.

Marc B
sumber
4
Saya juga / masih bingung. Katakanlah akun penerima memiliki $ 100 untuk memulai dan kami menambahkan pembayaran tagihan $ 20 dari akun kami. Pemahaman saya tentang transaksi adalah bahwa ketika mereka mulai, setiap operasi dalam transaksi melihat database dalam keadaan di awal transaksi. yaitu: sampai kami mengubahnya, akun penerima memiliki $ 100. Jadi ... ketika kita menambahkan $ 20, kita sebenarnya menetapkan saldo $ 120. Tetapi apa yang terjadi jika, selama transaksi kita, seseorang menguras rekening penerima hingga $ 0? Apakah ini bisa dicegah? Apakah mereka secara ajaib mendapatkan $ 120 lagi? Apakah ini sebabnya kunci juga dibutuhkan?
Russ
Ya, di situlah kunci berperan. Sistem yang tepat akan mengunci catatan sehingga tidak ada orang lain yang dapat memperbarui catatan saat transaksi sedang berlangsung. Sistem paranoid akan memasukkan kunci tanpa syarat pada catatan sehingga tidak ada yang bisa membaca saldo "basi" juga.
Marc B
1
Pada dasarnya, lihat transaksi sebagai hal yang mengamankan di dalam jalur kode Anda. Mengunci hal-hal yang aman di seluruh jalur kode "paralel". Sampai kebuntuan melanda ...
Marc B
1
@MarcB, Jadi mengapa kita harus melakukan penguncian secara eksplisit jika menggunakan transaksi saja sudah menjamin bahwa kunci sudah ada? Akankah ada kasus di mana kita harus melakukan penguncian eksplisit karena transaksi saja tidak cukup?
Pacerier
2
Jawaban ini tidak benar dan dapat menyebabkan kesimpulan yang salah. Pernyataan ini: "Kunci mencegah orang lain mengganggu catatan basis data apa pun yang Anda tangani. Transaksi membuat kesalahan" nanti "tidak mengganggu hal-hal" sebelumnya "yang telah Anda lakukan. Tidak satu pun yang dapat menjamin bahwa semuanya berjalan dengan baik di akhir. Tapi bersama-sama, mereka melakukannya. " - akan membuat Anda dipecat, itu sangat salah dan bodoh Lihat artikel: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems) dan dev.mysql.com/doc/refman/5.1/ id /…
Nikola Svitlica
14

Anda menginginkan SELECT ... FOR UPDATEatau SELECT ... LOCK IN SHARE MODEdi dalam transaksi, seperti yang Anda katakan, karena biasanya SELECT, tidak peduli apakah mereka dalam transaksi atau tidak, tidak akan mengunci tabel. Mana yang Anda pilih akan bergantung pada apakah Anda ingin transaksi lain dapat membaca baris itu saat transaksi Anda sedang berlangsung.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTtidak akan berhasil untuk Anda, karena transaksi lain masih bisa datang dan mengubah baris itu. Ini disebutkan tepat di bagian atas tautan di bawah.

Jika sesi lain secara bersamaan memperbarui tabel yang sama [...] Anda mungkin melihat tabel dalam keadaan yang tidak pernah ada di database.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

Alison R.
sumber
7

Konsep dan kunci transaksi berbeda. Namun, transaksi menggunakan kunci untuk membantunya mengikuti prinsip ACID. Jika Anda ingin tabel tersebut mencegah orang lain membaca / menulis pada titik waktu yang sama saat Anda membaca / menulis, Anda memerlukan kunci untuk melakukan ini. Jika Anda ingin memastikan integritas dan konsistensi data, sebaiknya gunakan transaksi. Saya pikir konsep campuran tingkat isolasi dalam transaksi dengan kunci. Silakan cari tingkat isolasi transaksi, SERIALIZE harus tingkat yang Anda inginkan.

tczhaodachuan.dll
sumber
Ini harus menjadi jawaban yang benar. Penguncian untuk mencegah kondisi balapan, dan transaksi untuk memperbarui beberapa tabel dengan data yang bergantung. Dua konsep yang sama sekali berbeda, meskipun transaksi menggunakan kunci.
Blue Water
6

Saya mengalami masalah yang sama saat mencoba IF NOT EXISTS ...dan kemudian melakukan INSERTyang menyebabkan kondisi balapan saat beberapa utas memperbarui tabel yang sama.

Saya menemukan solusi untuk masalah ini di sini: Bagaimana menulis pertanyaan INSERT IF NOT EXISTS dalam SQL standar

Saya menyadari ini tidak secara langsung menjawab pertanyaan Anda tetapi prinsip yang sama dalam melakukan pemeriksaan dan penyisipan sebagai satu pernyataan sangat berguna; Anda harus dapat memodifikasinya untuk melakukan pembaruan Anda.

Tony
sumber
2

Anda bingung dengan kunci & transaksi. Mereka adalah dua hal berbeda dalam RMDB. Kunci mencegah operasi bersamaan sementara transaksi berfokus pada isolasi data. Periksa ini artikel bagus untuk klarifikasi dan beberapa solusi anggun.

David
sumber
1
Kunci mencegah orang lain mengganggu rekaman yang sedang Anda kerjakan menjelaskan apa yang dilakukannya secara ringkas, dan transaksi mencegah kesalahan di kemudian hari (kesalahan orang lain yang membuat perubahan secara paralel) dari mengganggu hal-hal sebelumnya yang telah Anda lakukan (dengan mengizinkan rollback jika seseorang melakukan sesuatu secara paralel) cukup banyak menyimpulkan transaksi ... apa yang membingungkan tentang pemahamannya tentang topik ini?
steviesama
1

Saya akan menggunakan

START TRANSACTION WITH CONSISTENT SNAPSHOT;

untuk memulai, dan a

COMMIT;

untuk mengakhiri.

Apa pun yang Anda lakukan di antaranya diisolasi dari pengguna lain database Anda jika mesin penyimpanan Anda mendukung transaksi (yaitu InnoDB).

Martin Schapendonk
sumber
1
Kecuali tabel yang dia pilih tidak akan dikunci ke sesi lain kecuali dia secara khusus menguncinya (atau sampai UPDATE-nya terjadi), yang berarti sesi lain dapat datang dan memodifikasinya antara PILIH dan PEMBARUAN.
Alison R.Nov
Setelah membaca tentang MULAI TRANSAKSI DENGAN SNAPSHOT KONSISTEN di dokumentasi MySQL, saya tidak melihat di mana sebenarnya itu mengunci koneksi lain dari memperbarui baris yang sama. Pemahaman saya adalah bahwa ia akan melihat tabel dimulai pada awal transaksi. Jadi, jika transaksi lain sedang berlangsung, telah mendapatkan baris dan akan memperbaruinya, transaksi ke-2 masih akan melihat baris tersebut sebelum diperbarui. Oleh karena itu, berpotensi mencoba memperbarui baris yang sama dengan transaksi lainnya. Apakah itu benar atau saya melewatkan sesuatu dalam prosesnya?
Ryan
1
@Ryan Itu tidak melakukan penguncian; Anda benar. Mengunci (atau tidak) ditentukan oleh jenis operasi yang Anda lakukan (SELECT / UPDATE / DELETE).
Alison R.
4
Saya melihat. Itu memberikan konsistensi pembacaan transaksi Anda sendiri, tetapi tidak memblokir pengguna lain dari memodifikasi baris sebelum Anda melakukannya.
Martin Schapendonk