Cara menerapkan penguncian optimis dengan benar di MySQL

13

Bagaimana cara menerapkan penguncian optimis di MySQL dengan benar?

Tim kami telah menyimpulkan bahwa kami harus melakukan # 4 di bawah ini atau ada risiko bahwa utas lain dapat memperbarui versi catatan yang sama, tetapi kami ingin memvalidasi bahwa ini adalah cara terbaik untuk melakukannya.

  1. Buat bidang versi pada tabel yang ingin Anda gunakan penguncian optimis untuk misalnya kolom nama = "versi"
  2. Pada pilihan, pastikan untuk memasukkan kolom versi dan catat versi tersebut
  3. Pada pembaruan berikutnya ke catatan, pernyataan pembaruan harus mengeluarkan "di mana versi = X" di mana X adalah versi yang kami terima di # 2 dan mengatur bidang versi selama pernyataan pembaruan itu ke X + 1
  4. Lakukan SELECT FOR UPDATEpada catatan yang akan kami perbarui sehingga kami membuat serialisasi siapa yang dapat membuat perubahan pada catatan yang kami coba perbarui.

Untuk memperjelas, kami mencoba untuk mencegah dua utas yang memilih catatan yang sama di jendela waktu yang sama di mana mereka mengambil versi catatan yang sama dari saling menimpa satu sama lain jika mereka mencoba dan memperbarui catatan pada saat yang sama. Kami percaya bahwa kecuali kami # 4, ada kemungkinan, bahwa jika kedua utas memasukkan transaksi masing-masing pada saat yang sama (tetapi belum mengeluarkan pembaruan mereka), ketika mereka pergi untuk memperbarui, utas kedua yang akan menggunakan UPDATE ... di mana versi = X akan beroperasi pada data lama.

Apakah kita benar dalam berpikir bahwa kita harus melakukan penguncian pesimistis ini ketika memperbarui walaupun kita menggunakan bidang versi / penguncian optimis?

Praktik terbaik
sumber
Apa masalahnya? Anda menambah nomor versi dengan UPDATE Anda, maka UPDATE kedua akan gagal karena nomor versi tidak sama dengan ketika dibaca - yang Anda inginkan.
AndreKR
Apakah Anda yakin? Tidak jelas bahwa kecuali Anda mengatur tingkat isolasi transaksi ke pengaturan tertentu yang Anda akan benar-benar melihat pembaruan utas lainnya. Jika Anda berdua memasukkan transaksi pada saat yang sama, utas kedua dapat melihat dengan baik data LAMA saat akan melakukan pembaruan. MySQL tidak sekuat di arena ACID seperti yang dikatakan Oracle, karenanya mencari cara praktik terbaik untuk menerapkan penguncian optimistis di MySQL yang akan mencegah pembacaan / pembaruan yang kotor.
BestPractices
Tetapi kemudian transaksi akan gagal pula selama komit, kan?
AndreKR
Indikasi adalah bahwa seseorang ingin melakukan pemilihan untuk pembaruan untuk menghadapi situasi ini: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices
@BestPractices Anda perlu baik SELECT ... FOR UPDATE atau penguncian optimis demi baris versioning, tidak keduanya. Lihat detail sebagai jawaban.
Craig Ringer

Jawaban:

17

Pengembang Anda salah. Anda memerlukan salah satu SELECT ... FOR UPDATE atau versi baris, bukan keduanya.

Cobalah dan lihatlah. Buka tiga sesi MySQL (A), (B)dan (C)ke database yang sama.

Dalam (C)masalah:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

Di keduanya (A)dan (B)keluarkan UPDATEyang menguji dan menetapkan versi baris, ubah winnerteks di masing-masing sehingga Anda dapat melihat sesi mana yang:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

Sekarang (C), UNLOCK TABLES;untuk melepaskan kunci.

(A)dan (B)akan berlomba untuk kunci baris. Salah satunya akan menang dan mendapatkan kunci. Yang lain akan memblokir kunci. Pemenang yang mendapatkan kunci akan melanjutkan untuk mengubah baris. Dengan asumsi (A)adalah pemenang, Anda sekarang dapat melihat baris yang diubah (masih belum dikomit sehingga tidak terlihat oleh transaksi lain) dengan a SELECT * FROM test WHERE id = 1.

Sekarang COMMITdi sesi pemenang, katakanlah (A).

(B)akan mendapatkan kunci dan melanjutkan dengan pembaruan. Namun, versi tidak lagi cocok, sehingga tidak akan mengubah baris, seperti yang dilaporkan oleh hasil penghitungan baris. Hanya satu yang UPDATEmemiliki efek, dan aplikasi klien dapat dengan jelas melihat mana yang UPDATEberhasil dan yang gagal. Tidak diperlukan penguncian lebih lanjut.

Lihat log sesi di pastebin di sini . Saya menggunakan mysql --prompt="A> "dll untuk memudahkan membedakan antara sesi. Saya menyalin dan menempelkan output yang disisipkan dalam urutan waktu, jadi ini bukan output yang sepenuhnya mentah dan mungkin saya bisa membuat kesalahan saat menyalin dan menempelkannya. Uji sendiri untuk melihat.


Jika Anda belum menambahkan bidang versi baris, maka Anda harus SELECT ... FOR UPDATEdapat memastikan pemesanan.

Jika Anda memikirkannya, a benarSELECT ... FOR UPDATE - benar berlebihan jika Anda segera melakukan UPDATEtanpa menggunakan kembali data dari SELECT, atau jika Anda menggunakan versi baris. The UPDATEakan mengambil kunci pula. Jika orang lain memperbarui baris antara tulisan Anda dan tulisan selanjutnya, versi Anda tidak akan cocok lagi sehingga pembaruan Anda akan gagal. Begitulah cara penguncian optimis.

Tujuannya SELECT ... FOR UPDATEadalah:

  • Untuk mengelola pemesanan kunci untuk menghindari kebuntuan; dan
  • Untuk memperpanjang rentang kunci baris ketika Anda ingin membaca data dari baris, ubahlah dalam aplikasi, dan tulis baris baru yang didasarkan pada yang asli tanpa harus menggunakan SERIALIZABLEisolasi atau versi baris.

Anda tidak perlu menggunakan penguncian optimis (versi baris) dan SELECT ... FOR UPDATE. Gunakan satu atau yang lain.

Craig Ringer
sumber
Terima kasih Craig. Anda benar - pengembang salah. Terima kasih sudah menjalankan tes ini.
BestPractices
Bagaimana dengan SQL server? Apakah selalu ada kunci yang diperoleh pada baris yang diperbarui secara independen dari tingkat isolasi transaksi?
plalx
@plalx Nah, apa yang dikatakan dokumentasi? Apa yang terjadi jika Anda menjalankan tes interaktif seperti ini?
Craig Ringer
@CraigRinger, apa yang akan terjadi jika B mendapatkan kunci sebelum komit tetapi setelah pembaruan A?
MengT
1
@ MengT Itu tidak bisa, itu sebabnya itu kunci.
Craig Ringer
0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

Tidak diperlukan Kunci (bukan meja, bukan transaksi), atau bahkan yang diinginkan:

  • UPDATE bersifat atomik
  • LAST_INSERT_ID () khusus untuk sesi, karenanya aman untuk thread.
Rick James
sumber