Kata pengantar
Aplikasi kami menjalankan beberapa utas yang menjalankan DELETE
kueri secara paralel. Kueri mempengaruhi data yang terisolasi, yaitu seharusnya tidak ada kemungkinan yang DELETE
terjadi bersamaan pada baris yang sama dari utas yang terpisah. Namun, per dokumentasi MySQL menggunakan apa yang disebut kunci 'tombol selanjutnya' untuk DELETE
pernyataan, yang mengunci kunci yang cocok dan beberapa celah. Hal ini mengarah ke jalan buntu dan satu-satunya solusi yang kami temukan adalah menggunakan READ COMMITTED
tingkat isolasi.
Masalah
Masalah muncul ketika menjalankan DELETE
pernyataan kompleks dengan JOIN
s dari tabel besar. Dalam kasus tertentu, kami memiliki tabel dengan peringatan yang hanya memiliki dua baris, tetapi kueri harus menghapus semua peringatan milik beberapa entitas tertentu dari dua INNER JOIN
tabel ed yang terpisah . Kueri adalah sebagai berikut:
DELETE pw
FROM proc_warnings pw
INNER JOIN day_position dp
ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=? AND dp.dirty_data=1
Ketika tabel day_position cukup besar (dalam kasus pengujian saya ada 1448 baris) maka transaksi apa pun bahkan dengan READ COMMITTED
mode isolasi memblokir seluruh proc_warnings
tabel.
Masalah ini selalu direproduksi pada data sampel ini - http://yadi.sk/d/QDuwBtpW1BxB9 baik di MySQL 5.1 (diperiksa pada 5.1.59) dan MySQL 5.5 (diperiksa pada MySQL 5.5.24).
EDIT: Data sampel tertaut juga berisi skema dan indeks untuk tabel kueri, direproduksi di sini untuk kenyamanan:
CREATE TABLE `proc_warnings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`warning` varchar(2048) NOT NULL,
PRIMARY KEY (`id`),
KEY `proc_warnings__transaction` (`transaction_id`)
);
CREATE TABLE `day_position` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_day_id` int(10) unsigned DEFAULT NULL,
`dirty_data` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `day_position__trans` (`transaction_id`),
KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;
CREATE TABLE `ivehicle_days` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d` date DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
KEY `ivehicle_days__d` (`d`)
);
Kueri per transaksi adalah sebagai berikut:
Transaksi 1
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
Transaksi 2
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;
Salah satunya selalu gagal dengan kesalahan 'Kunci waktu tunggu tunggu terlampaui ...'. The information_schema.innodb_trx
berisi baris berikut:
| trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2' | '3089' | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING' | '2012-12-12 19:58:02' | NULL | NULL | '7' | '3087' | NULL |
information_schema.innodb_locks
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
Seperti yang saya lihat, kedua query menginginkan X
kunci eksklusif pada baris dengan primary key = 53. Namun, tidak satu pun dari mereka harus menghapus baris dari proc_warnings
tabel. Saya hanya tidak mengerti mengapa indeks terkunci. Selain itu, indeks tidak dikunci ketika proc_warnings
tabel kosong atau day_position
tabel berisi jumlah baris yang lebih sedikit (yaitu seratus baris).
Investigasi lebih lanjut adalah untuk menyelidiki pertanyaan yang EXPLAIN
sama SELECT
. Ini menunjukkan bahwa pengoptimal kueri tidak menggunakan indeks ke proc_warnings
tabel kueri dan itulah satu-satunya alasan yang dapat saya bayangkan mengapa ia memblokir seluruh indeks kunci primer.
Kasing sederhana
Masalah juga dapat direproduksi dalam kasus yang lebih sederhana ketika hanya ada dua tabel dengan beberapa catatan, tetapi tabel anak tidak memiliki indeks pada kolom ref tabel induk.
Buat parent
tabel
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Buat child
tabel
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Isi tabel
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
Tes dalam dua transaksi paralel:
Transaksi 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 1;
Transaksi 2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 2;
Bagian umum dalam kedua kasus adalah MySQL tidak menggunakan indeks. Saya percaya itulah alasan kunci seluruh meja.
Solusi Kami
Satu-satunya solusi yang dapat kita lihat untuk saat ini adalah meningkatkan batas waktu tunggu kunci default dari 50 detik menjadi 500 detik untuk membiarkan utas selesai berbenah. Kemudian jagalah agar tetap bersilangan.
Setiap bantuan dihargai.
day_position
biasanya terdapat pada tabel, ketika mulai berjalan sangat lambat sehingga Anda harus menabrak batas waktu habis menjadi 500 detik? 2) Berapa lama untuk menjalankan ketika Anda hanya memiliki data sampel?