Предисловие
Наше приложение запускает несколько потоков, которые выполняют 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) Сколько времени требуется для запуска, когда у вас есть только данные образца?