MySQL
ВЫБРАТЬ ... ДЛЯ ОБНОВЛЕНИЯ с ОБНОВЛЕНИЕМ
Использование транзакций с InnoDB (автоматическая фиксация отключена), a SELECT ... FOR UPDATE
позволяет одному сеансу временно блокировать определенную запись (или записи), чтобы другой сеанс не мог ее обновить.Затем в рамках той же транзакции сеанс может фактически выполнить UPDATE
для той же записи и зафиксировать или откатить транзакцию.Это позволит вам заблокировать запись, чтобы ни один другой сеанс не мог ее обновить, хотя, возможно, вы выполняете какую-то другую бизнес-логику.
Это достигается с помощью блокировки.InnoDB использует индексы для блокировки записей, поэтому блокировка существующей записи кажется простой - просто заблокируйте индекс для этой записи.
SELECT ... FOR UPDATE с INSERT
Однако, чтобы использовать SELECT ... FOR UPDATE
с INSERT
, как заблокировать индекс для записи, которая еще не существует?Если вы используете уровень изоляции по умолчанию REPEATABLE READ
, InnoDB также будет использовать блокировки gap .Пока вы знаете, что id
(или даже диапазон идентификаторов) для блокировки, InnoDB может заблокировать разрыв, так что никакая другая запись не может быть вставлена в этот разрыв, пока мы не закончим с этим.ваш столбец id
был столбцом с автоинкрементом, тогда SELECT ... FOR UPDATE
с INSERT INTO
будет проблематичным, потому что вы не будете знать, что такое новый id
, пока не вставите его.Однако, поскольку вы знаете, id
, который вы хотите вставить, SELECT ... FOR UPDATE
с INSERT
будет работать.
CAVEAT
На уровне изоляции по умолчанию,SELECT ... FOR UPDATE
для несуществующей записи не блокирует другие транзакции.Таким образом, если две транзакции выполнят SELECT ... FOR UPDATE
для одной и той же несуществующей индексной записи, они обе получат блокировку, и ни одна из транзакций не сможет обновить запись.Фактически, если они попытаются, будет обнаружена тупик.
Поэтому, если вы не хотите иметь дело с тупиком, вы можете просто сделать следующее:
INSERTINTO ...
Запустите транзакцию и выполните INSERT
.Выполните свою бизнес-логику и либо подтвердите, либо откатите транзакцию.Как только вы сделаете INSERT
для несуществующего индекса записи в первой транзакции, все другие транзакции заблокируют, если они попытаются INSERT
запись с тем же уникальным индексом.Если вторая транзакция попытается вставить запись с тем же индексом после того, как первая транзакция совершит вставку, то она получит ошибку «дубликат ключа».Обрабатывать соответственно.
SELECT ... LOCK IN SHARE MODE
Если вы выбрали с LOCK IN SHARE MODE
перед INSERT
, если предыдущая транзакция вставила эту записьно еще не зафиксировано, SELECT ... LOCK IN SHARE MODE
будет блокироваться до завершения предыдущей транзакции.
Таким образом, чтобы уменьшить вероятность дублирования ошибок ключа, особенно если вы удерживаете блокировки некоторое время во время выполнения бизнес-логики перед фиксациейили их откат:
SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
- Если записи не возвращены, то
INSERT INTO FooBar (foo, bar) VALUES (?, ?)