Предотвращает ли «выбор для обновления» вставку других соединений при отсутствии строки - PullRequest
15 голосов
/ 30 августа 2010

Меня интересует, заблокирует ли запрос select for update несуществующую строку.

, например

Таблица FooBar с двумя столбцами, foo и bar, foo имеет уникальный индекс

  • Запрос на выдачу select bar from FooBar where foo = ? for update
  • Если запрос возвращает ноль строк
    • Запрос на выдачу insert into FooBar (foo, bar) values (?, ?)

Теперь возможно, что вставка вызовет нарушение индекса, или select for update предотвращает это?

Интересует поведение на SQLServer (2005/8), Oracle и MySQL.

Ответы [ 5 ]

7 голосов
/ 21 января 2014

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 будет блокироваться до завершения предыдущей транзакции.

Таким образом, чтобы уменьшить вероятность дублирования ошибок ключа, особенно если вы удерживаете блокировки некоторое время во время выполнения бизнес-логики перед фиксациейили их откат:

  1. SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
  2. Если записи не возвращены, то
  3. INSERT INTO FooBar (foo, bar) VALUES (?, ?)
7 голосов
/ 30 августа 2010

В Oracle SELECT ... FOR UPDATE не влияет на несуществующую строку (оператор просто вызывает исключение No Data Found). Оператор INSERT предотвратит дублирование значений уникального / первичного ключа. Любые другие транзакции, пытающиеся вставить те же значения ключа, будут блокироваться до тех пор, пока первая транзакция не зафиксируется (в это время заблокированная транзакция получит ошибку дублирующего ключа) или откатится (в это время заблокированная транзакция продолжится).

2 голосов
/ 30 августа 2010

В Oracle:

Сессия 1

create table t (id number);
alter table t add constraint pk primary key(id);

SELECT *
FROM t
WHERE id = 1
FOR UPDATE;
-- 0 rows returned
-- this creates row level lock on table, preventing others from locking table in exclusive mode

Сессия 2

SELECT *
FROM t 
FOR UPDATE;
-- 0 rows returned
-- there are no problems with locking here

rollback; -- releases lock


INSERT INTO t
VALUES (1);
-- 1 row inserted without problems
1 голос
/ 30 августа 2010

Я написал подробный анализ этой вещи на SQL Server: Разработка модификаций, которые выдерживают параллелизм

В любом случае, вам нужно использовать СЕРИАЛИЗИРУЕМЫЙ уровень изоляции, и вам действительно нужно провести стресс-тест.

0 голосов
/ 30 августа 2010

SQL Server имеет только FOR UPDATE как часть курсора.И это относится только к операторам UPDATE, которые связаны с текущей строкой в ​​курсоре.

Таким образом, FOR UPDATE не имеет отношения к INSERT.Поэтому я думаю, что ваш ответ заключается в том, что он не применим в SQL Server.

Теперь возможно моделировать поведение FOR UPDATE с помощью транзакций и стратегий блокировки.Но это может быть больше, чем вы ищете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...