Передмова
У нашому додатку працює кілька потоків, які виконують DELETE
запити паралельно. Запити впливають на поодинокі дані, тобто не повинно бути ймовірності того, що паралельний запис DELETE
відбувається в одних і тих же рядках з окремих потоків. Однак, відповідно до документації, MySQL використовує так званий замок "next-key" для DELETE
операторів, який блокує як відповідність ключа, так і деякий пробіл. Ця річ призводить до глухих замків, і єдине знайдене нами рішення - використовувати READ COMMITTED
рівень ізоляції.
Проблема
Проблема виникає при виконанні складних DELETE
операторів з JOIN
величезними таблицями. У конкретному випадку у нас є таблиця з попередженнями, яка містить лише два рядки, але запит повинен видалити всі попередження, які належать певним особам, з двох окремих INNER JOIN
таблиць редакції. Запит такий:
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
Коли таблиця day_position досить велика (у моєму тестовому випадку є 1448 рядків), то будь-яка транзакція навіть у READ COMMITTED
режимі ізоляції блокує всю proc_warnings
таблицю.
Проблема завжди відтворюється на цих зразкових даних - http://yadi.sk/d/QDuwBtpW1BxB9 як у MySQL 5.1 (перевірено 5.1.59), так і в MySQL 5.5 (перевірено на MySQL 5.5.24).
EDIT: Зв'язані зразки даних також містять схему та індекси для таблиць запитів, відтворені тут для зручності:
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`)
);
Запити на одну транзакцію такі:
Угода 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;
Транзакція 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;
Один із них завжди не працює із помилкою "Заблокований час очікування очікування перевищено ...". information_schema.innodb_trx
Містить наступні рядки:
| 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' |
Як я бачу, обидва запити хочуть ексклюзивного X
блокування в рядку з первинним ключем = 53. Однак жоден з них не повинен видаляти рядки з proc_warnings
таблиці. Я просто не розумію, чому індекс заблокований. Більше того, індекс не блокується ні тоді, коли proc_warnings
таблиця порожня, ні day_position
таблиця містить меншу кількість рядків (тобто сто рядків).
Подальше розслідування повинно було виконати EXPLAIN
аналогічний SELECT
запит. Це показує, що оптимізатор запитів не використовує індекс для proc_warnings
таблиці запитів, і це єдина причина, що я можу уявити, чому він блокує весь індекс первинного ключа.
Спрощений випадок
Випуск також може бути відтворений у більш простому випадку, коли є лише дві таблиці з парою записів, але дочірня таблиця не має індексу в таблиці стовпців рефінансування батьківської таблиці.
Створити parent
таблицю
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Створити child
таблицю
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Заповнення таблиць
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
Тест у двох паралельних транзакціях:
Угода 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;
Транзакція 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;
Загальна частина в обох випадках полягає в тому, що MySQL не використовує індекси. Я вважаю, що це причина блокування всієї таблиці.
Наше рішення
Єдине рішення, яке ми можемо побачити зараз, - збільшити час очікування блокування за замовчуванням з 50 секунд до 500 секунд, щоб дозволити очищення нитки. Потім тримайте пальці схрещеними.
Будь-яка допомога вдячна.
day_position
зазвичай містить таблиця, коли вона починає працювати так повільно, що вам доведеться збільшити обмеження на час очікування до 500 сек? 2) Скільки часу потрібно запустити, коли у вас є лише вибіркові дані?