Penguncian baris InnoDB - cara menerapkan

13

Saya sudah mencari-cari sekarang, membaca situs mysql dan saya masih tidak bisa melihat persis bagaimana cara kerjanya.

Saya ingin memilih dan mengunci hasil untuk menulis, menulis perubahan dan melepaskan kunci. audocommit aktif.

skema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime) 

Pilih item dengan status menunggu keputusan, dan perbarui untuk berfungsi. Gunakan tulisan eksklusif untuk memastikan barang yang sama tidak diambil dua kali.

begitu;

"SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR WRITE"

dapatkan id dari hasilnya

"UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>

Apakah saya perlu melakukan sesuatu untuk melepaskan kunci, dan apakah berfungsi seperti yang saya lakukan di atas?

Wizzard
sumber

Jawaban:

26

Yang Anda inginkan adalah SELECT ... FOR UPDATE dari dalam konteks transaksi. PILIH UNTUK PEMBARUAN menempatkan kunci eksklusif pada baris yang dipilih, sama seperti jika Anda menjalankan UPDATE. Itu juga secara implisit berjalan di tingkat isolasi BACA BERKOMITMEN terlepas dari apa tingkat isolasi secara eksplisit ditetapkan. Perlu diketahui bahwa SELECT ... UNTUK PEMBARUAN sangat buruk untuk konkurensi dan hanya boleh digunakan jika benar-benar diperlukan. Ini juga memiliki kecenderungan untuk berkembang biak dalam basis kode karena orang memotong dan menempel.

Berikut adalah contoh sesi dari database Sakila yang menunjukkan beberapa perilaku kueri FOR UPDATE.

Pertama, supaya kami jelas, atur tingkat isolasi transaksi ke REPEATABLE READ. Ini biasanya tidak perlu, karena ini adalah level isolasi default untuk InnoDB:

session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)    

Di sesi lain, perbarui baris ini. Linda menikah dan mengganti namanya:

session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Kembali di sesi1, karena kami berada di READATABLE READ, Linda masih LINDA WILLIAMS:

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)

Tapi sekarang, kami ingin akses eksklusif ke baris ini, jadi kami memanggil UPDATE di baris tersebut. Perhatikan bahwa kita sekarang mendapatkan versi terbaru dari baris itu, yang diperbarui di sesi2 di luar transaksi ini. Itu bukan BACA ULANG, itu BACA BERKOMITMEN

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | BROWN     |
+------------+-----------+
1 row in set (0.00 sec)

Mari kita coba set kunci di sesi1. Perhatikan bahwa session2 tidak dapat memperbarui baris.

session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

Tapi kita masih bisa memilihnya

session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address           |
+-------------+------------+-----------+------------+-------------------+
|           3 | LINDA      | BROWN     |          7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)

Dan kita masih dapat memperbarui tabel anak dengan hubungan kunci asing

session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

session1> COMMIT;

Efek samping lainnya adalah Anda meningkatkan kemungkinan menyebabkan jalan buntu.

Dalam kasus spesifik Anda, Anda mungkin ingin:

BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;

Jika bagian "lakukan beberapa hal lain" tidak perlu dan Anda sebenarnya tidak perlu menyimpan informasi tentang baris tersebut, maka SELECT FOR UPDATE tidak perlu dan boros dan Anda bisa menjalankan pembaruan:

UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;

Semoga ini masuk akal.

Aaron Brown
sumber
3
Terima kasih. Tampaknya tidak menyelesaikan masalah saya, ketika dua utas masuk dengan "SELECT id FROM itemsWHERE status= 'pending' LIMIT 1 UNTUK PEMBARUAN;" dan mereka berdua melihat baris yang sama, maka yang satu akan mengunci yang lain. Saya berharap entah bagaimana itu akan dapat melewati baris yang terkunci dan kebagian item berikutnya yang tertunda ..
Wizzard
1
Sifat dasar dari database adalah bahwa mereka mengembalikan data yang konsisten. Jika Anda menjalankan kueri itu dua kali sebelum nilainya diperbarui, Anda akan mendapatkan hasil yang sama kembali. Tidak ada "dapatkan saya nilai pertama yang cocok dengan kueri ini, kecuali baris terkunci" ekstensi SQL yang saya sadari. Ini kedengarannya mencurigakan seperti Anda menerapkan antrian di atas basis data relasional. Apakah itu masalahnya?
Aaron Brown
Harun; ya itulah yang saya coba lakukan. Saya telah melihat menggunakan sesuatu seperti tukang gigi - tapi itu bangkrut. Anda punya hal lain dalam pikiran?
Wizzard
Saya pikir Anda harus membaca ini: engineyard.com/blog/2011/... - untuk antrian pesan, ada banyak dari mereka di luar sana tergantung pada bahasa klien pilihan Anda. ActiveMQ, Resque (Ruby + Redis), ZeroMQ, RabbitMQ, dll.
Aaron Brown
Bagaimana saya membuatnya sehingga sesi 2 memblokir membaca sampai pembaruan di sesi 1 dilakukan?
CMCDragonkai
2

Jika Anda menggunakan mesin penyimpanan InnoDB menggunakan penguncian tingkat baris. Sehubungan dengan multi-versi, ini menghasilkan konkurensi kueri yang baik karena tabel yang diberikan dapat dibaca dan dimodifikasi oleh klien yang berbeda secara bersamaan. Properti concurrency tingkat baris adalah sebagai berikut:

Klien yang berbeda dapat membaca baris yang sama secara bersamaan.

Klien yang berbeda dapat mengubah baris yang berbeda secara bersamaan.

Klien yang berbeda tidak dapat mengubah baris yang sama secara bersamaan. Jika satu transaksi mengubah satu baris, transaksi lainnya tidak dapat mengubah baris yang sama hingga transaksi pertama selesai. Transaksi lain juga tidak dapat membaca baris yang dimodifikasi, kecuali mereka menggunakan tingkat isolasi BACA YANG TIDAK DICARI. Artinya, mereka akan melihat baris asli yang tidak dimodifikasi.

Pada dasarnya, Anda tidak harus menentukan penguncian eksplisit InnoDB menanganinya iteslf meskipun dalam beberapa situasi Anda mungkin harus memberikan detail kunci eksplisit tentang kunci eksplisit diberikan di bawah ini:

Daftar berikut ini menjelaskan jenis kunci yang tersedia dan efeknya:

BACA

Mengunci meja untuk membaca. Kunci BACA mengunci tabel untuk kueri baca seperti SELECT yang mengambil data dari tabel. Itu tidak memungkinkan operasi tulis seperti INSERT, DELETE, atau UPDATE yang memodifikasi tabel, bahkan oleh klien yang memegang kunci. Ketika sebuah meja dikunci untuk dibaca, klien lain dapat membaca dari tabel pada saat yang sama, tetapi tidak ada klien yang dapat menulisnya. Klien yang ingin menulis ke tabel yang terkunci-baca harus menunggu sampai semua klien yang sedang membaca darinya selesai dan melepaskan kunci mereka.

MENULIS

Mengunci meja untuk menulis. Kunci WRITE adalah kunci eksklusif. Ini bisa diperoleh hanya ketika tabel tidak digunakan. Setelah diperoleh, hanya klien yang memegang kunci tulis yang dapat membaca dari atau menulis di atas meja. Klien lain tidak dapat membaca atau menulis untuk itu. Tidak ada klien lain yang dapat mengunci tabel untuk membaca atau menulis.

BACA LOKAL

Mengunci meja untuk dibaca, tetapi memungkinkan sisipan bersamaan. Sisipan bersamaan adalah pengecualian untuk prinsip "pembaca blok penulis". Ini hanya berlaku untuk tabel MyISAM. Jika tabel MyISAM tidak memiliki lubang di tengah yang dihasilkan dari catatan yang dihapus atau diperbarui, sisipan selalu terjadi di akhir tabel. Dalam hal itu, klien yang membaca dari tabel dapat menguncinya dengan kunci BACA LOKAL untuk memungkinkan klien lain untuk memasukkan ke dalam tabel sementara klien memegang kunci baca membaca dari itu. Jika tabel MyISAM memang memiliki lubang, Anda dapat menghapusnya dengan menggunakan OPTIMIZE TABLE untuk mendefrag tabel.

Mahesh Patil
sumber
Terima kasih atas jawabannya. Karena saya memiliki tabel ini dan 100 klien memeriksa barang-barang yang tertunda, saya mendapatkan banyak tabrakan - 2-3 klien mendapatkan baris tertunda yang sama. Kunci meja untuk memperlambat.
Wizzard
0

Alternatif lain adalah dengan menambahkan kolom yang menyimpan waktu kunci sukses terakhir dan kemudian hal lain yang ingin mengunci baris perlu menunggu sampai dihapus atau 5 menit (atau apa pun) telah berlalu.

Sesuatu seperti...

Schema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime)
lastlock (int)

lastlock adalah sebuah int karena ia menyimpan cap waktu unix sebagai lebih mudah (dan mungkin lebih cepat) untuk dibandingkan.

// Maafkan semantiknya, aku belum mengecek mereka berlari secara akut, tetapi mereka seharusnya cukup dekat jika tidak.

UPDATE items 
  SET lastlock = UNIX_TIMESTAMP() 
WHERE 
  lastlock = 0
  OR (UNIX_TIMESTAMP() - lastlock) > 360;

Kemudian periksa untuk melihat berapa banyak baris yang diperbarui, karena baris tidak dapat diperbarui oleh dua proses sekaligus, jika Anda memperbarui baris, Anda mendapatkan kunci. Dengan asumsi Anda menggunakan PHP, Anda akan menggunakan mysql_affected_rows (), jika kembali dari yang 1, Anda berhasil menguncinya.

Kemudian Anda dapat memperbarui kunci terakhir ke 0 setelah Anda melakukan apa yang perlu Anda lakukan, atau menjadi malas dan menunggu 5 menit ketika upaya kunci berikutnya akan berhasil.

EDIT: Anda mungkin perlu sedikit kerja untuk memeriksanya berfungsi seperti yang diharapkan di sekitar perubahan waktu musim panas karena jam akan kembali satu jam, mungkin membuat cek kosong. Anda harus memastikan cap waktu unix berada di UTC - yang mungkin saja.

Steve Childs
sumber
-1

Atau, Anda bisa memecah bidang rekaman untuk memungkinkan penulisan paralel dan memotong penguncian baris (gaya pasangan json terfragmentasi). Jadi jika satu bidang catatan baca majemuk adalah bilangan bulat / nyata, Anda dapat memiliki fragmen 1-8 dari bidang tersebut (8 tulis catatan / baris yang berlaku). Kemudian jumlah fragmen round-robin setelah setiap penulisan menjadi pencarian baca yang terpisah. Ini memungkinkan hingga 8 pengguna bersamaan secara paralel.

Karena Anda hanya bekerja dengan masing-masing fragmen yang membuat total parsial, tidak ada tabrakan dan pembaruan paralel yang sebenarnya (yaitu Anda menulis mengunci setiap fragmen daripada seluruh catatan baca yang disatukan). Ini hanya bekerja pada bidang numerik saja. Sesuatu yang mengandalkan modifikasi matematis untuk menyimpan hasilnya.

Dengan demikian, beberapa fragmen tulis per bidang baca terpadu per catatan baca terpadu. Fragmen numerik ini juga cocok untuk ECC, enkripsi, dan transfer / penyimpanan level blok. Semakin banyak fragmen tulis, semakin besar kecepatan akses tulis paralel / konkuren pada data jenuh.

MMORPG sangat menderita dengan masalah ini, ketika sejumlah besar pemain mulai saling memukul dengan skill Area of ​​Effect. Semua pemain itu semua perlu menulis / memperbarui setiap pemain lainnya pada waktu yang bersamaan, secara paralel, menciptakan badai penguncian baris tulis pada catatan pemain yang bersatu.

Mick Saunders
sumber