Оператор InnoDB SELECT ... FOR UPDATE, блокирующий все строки в таблице - PullRequest
9 голосов
/ 14 июля 2011

MySQL Server версии 5.1.41 с включенным плагином InnoDB. У меня есть следующие три таблицы для счетов-фактур: счета-фактуры, invoice_components и invoice_expenses. Таблица счетов имеет первичный ключ invoice_id. И invoice_components, и invoice_expenses связаны с накладными таблиц с invoice_id как неуникальным foreign_key (каждый счет может иметь более одного компонента и более одного расхода). Обе таблицы имеют индекс BTREE для этого внешнего ключа.

У меня есть следующие транзакции:

транзакция 1

START TRANSACTION; 
SELECT * FROM invoices WHERE invoice_id = 18 FOR UPDATE; 
SELECT * FROM invoice_components WHERE invoice = 18 FOR UPDATE; 
SELECT * FROM invoice_expenses WHERE invoice = 18 FOR UPDATE; 

Все работает нормально для первой транзакции, строки выбираются и блокируются.

транзакция 2

START TRANSACTION; 
SELECT * FROM invoices WHERE invoice_id = 19 FOR UPDATE; 
SELECT * FROM invoice_components WHERE invoice = 19 FOR UPDATE; 
SELECT * FROM invoice_expenses WHERE invoice = 19 FOR UPDATE; 

Вторая транзакция возвращает ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction для третьего запроса.

То же самое происходит, когда я пытаюсь ВЫБРАТЬ ... ДЛЯ ОБНОВЛЕНИЯ другие счета, их компоненты и расходы. Кажется, первая транзакция заблокировала все строки в таблице invoice_expenses. Есть идеи, почему это происходит?

Дополнительная информация

Транзакция 2 запускается после третьего запроса транзакции 1. На сервере нет других пользователей, соединений или транзакций.

Проблема возникает на уровне изоляции транзакции REPEATABLE READ по умолчанию. Это исправлено, если перейти на уровень READ COMMITTED. Это решение, но оно по-прежнему не объясняет, почему проблема возникает с invoice_expenses, а не с invoice_components.

Ответы [ 3 ]

10 голосов
/ 19 июля 2011

Я подозреваю, что это связано с блокировками пробелов и блокировками следующего ключа и различиями в поведении REPEATABLE READ :

Выдержки из документов MySQL: Синтаксис SET TRANSACTION

Для блокировки чтения (SELECT с FOR UPDATE или LOCK IN SHARE MODE), UPDATE иОператоры DELETE, блокировка зависит от того, использует ли инструкция уникальный индекс с уникальным условием поиска, или условие поиска типа диапазона.Для уникального индекса с уникальным условием поиска InnoDB блокирует только найденную индексную запись, а не промежуток перед ней.Для других условий поиска InnoDB блокирует сканированный диапазон индекса, используя блокировки пробела или блокировки следующего ключа (разрыв плюс запись индекса) , чтобы блокировать вставки другими сеансами в промежутки, охватываемые диапазоном.

и READ COMMITTED :

Примечание. В MySQL 5.1, если используется уровень изоляции READ COMMITTED или включена системная переменная innodb_locks_unsafe_for_binlog, нет блокировки InnoDB за исключением проверки ограничений внешнего ключа и проверки дубликата ключа.Кроме того, блокировки записей для несовпадающих строк снимаются после того, как MySQL оценил условие WHERE.

Возможно, OP сможет сообщить нам состояние переменной innodb_locks_unsafe_for_binlog system и, если произойдет такая же блокировкапри изменении настройки этой переменной.

Также, если такая же блокировка происходит с непоследовательными идентификаторами, такими как 18 и 20, или 18 и 99

0 голосов
/ 16 июня 2014

"Для записей индекса поисковые запросы, SELECT ... FROM ... FOR UPDATE блокируют другие сеансы от выполнения SELECT ... FROM ... LOCK IN SHARE MODE или от чтения на определенных уровнях изоляции транзакции. Последовательные чтения будутигнорировать любые блокировки, установленные для записей, которые существуют в представлении для чтения "

Что это за определенные блокировки, которые можно применять с помощью select для обновления, чтобы другие сеансы не могли читать заблокированные записи?

0 голосов
/ 14 июля 2011

Вы используете транзакцию; autocommit не отключает транзакции, он просто автоматически фиксирует их в конце операторов, для которых нет явного start transaction.

Происходит следующее: какой-то другой поток слишком долго удерживает блокировку записи для какой-либо записи (вы обновляете каждую запись в таблице!), И время вашего потока истекло.

Более подробную информацию о событии можно увидеть, выдав «SHOW ENGINE INNODB STATUS» после события. Идеально делать это на тихой тестовой машине.

...