Предисловие
Наше приложение запускает несколько потоков, которые выполняют DELETEзапросы параллельно. Запросы влияют на изолированные данные, т. Е. Не должно быть вероятности того, что одновременное выполнение будет DELETEпроисходить в одних и тех же строках из отдельных потоков. Однако согласно документации MySQL использует так называемую блокировку следующего ключа для DELETEоператоров, которая блокирует как соответствующий ключ, так и некоторый пробел. Это приводит к тупикам, и единственное решение, которое мы нашли, это использовать READ COMMITTEDуровень изоляции.
Проблема
Проблема возникает при выполнении сложных DELETEоператоров с JOINогромными таблицами. В конкретном случае у нас есть таблица с предупреждениями, которая имеет только две строки, но запрос должен удалить все предупреждения, которые принадлежат некоторым конкретным объектам, из двух отдельных INNER JOINтаблиц ed. Запрос выглядит следующим образом:
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).
РЕДАКТИРОВАТЬ: Связанные примеры данных также содержат схему и индексы для таблиц запросов, воспроизведенные здесь для удобства:
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) Сколько времени требуется для запуска, когда у вас есть только данные образца?