it-swarm.asia

Penguncian baris InnoDB - cara menerapkan

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 pending, 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?

13
Wizzard

Yang Anda inginkan adalah PILIH ... UNTUK PEMBARUAN 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 itu. Perhatikan bahwa kita sekarang mendapatkan versi terbaru dari baris itu, yang diperbarui di sesi2 di luar transaksi ini. Itu bukan BACA berulang, 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.

26
Aaron Brown

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 memodifikasi 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 rincian 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 telah selesai dan melepaskan kunci mereka.

TULIS

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

2
Mahesh Patil

Alternatif lain adalah 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 pengembalian dari itu adalah 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.

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

0
Steve Childs