Почему я не должен использовать "Repeatable Read" с блокировкой чтения (выберите для обновления)?
В документе Mysql: " https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.html"
В нем упоминалось: "Если вы используете блокировку чтения (SELECT ... FOR UPDATE или SELECT ... LOCK IN SHARE MODE), попробуйте использовать более низкий уровень изоляции, такой как READ COMMITTED".
Может кто-нибудь сказать мне, почему я не могу использовать "Repeatable Read"? Пример будет хорошим.
ура
2 ответа
InnoDB избегает использования определенных видов блокировок, если вы используете read-commit. Это может помочь вам избежать тупика.
Я разработал целую презентацию об этом: блокировка InnoDB, объясненная фигурками.
Но вы практически никогда не сможете избежать 100% случаев тупиков. Они не ошибка, они естественная часть параллельных систем. Вы можете уменьшить частоту возникновения взаимоблокировок, но вы также можете привыкнуть к их получению. Разработайте свой код, чтобы перехватывать исключения и повторять операции с базами данных, когда они зашли в тупик.
Причина в том, что если запись не существует в таблице, это похоже на получение блокировки следующего ключа в общем режиме (или что-то подобное - это нигде не документировано) для индексной записи, следующей за несуществующей записью, когда активен режим изоляции.
Давайте попробуем пример для простой таблицы
t
что пусто в
REPEATABLE READ
режим изоляции.
t1> SELECT * FROM t WHERE id = 1 FOR UPDATE;
no rows found, next-key lock acquired in shared mode
t2> SELECT * FROM t WHERE id = 1 FOR UPDATE;
no rows found, next-key lock acquired in shared mode
t1> INSERT INTO t (id) VALUES (1);
transaction t1 is blocked by t2
t2> INSERT INTO t (id) VALUES (1);
transaction t2 is blocked by t1 - deadlock
Взаимная блокировка произойдет, даже если будет использован второй SELECT и INSERT, потому что он также попадает в тот же пробел, заблокированный , выполненный в . А если таблица пуста, то этот промежуток равен бесконечности. Если таблица не пуста, вероятность взаимоблокировки меньше при вставке разных записей, но все же велика (это зависит от того, сколько пробелов в таблице и как часто вы вставляете до конца таблицы - самый большой пробел).
Это происходит потому, что from и not блокируют друг друга, когда запись не существует. Для существующей записи он получает X (монопольную) блокировку записи в , поэтому будет заблокирован до фиксации или отката. Но если записи не существует - она получает блокировку следующего ключа S (shared) на разрыв (я не уверен, что это действительно блокировка S (нигде не документировано), но как еще MySQL позволяет получить 2 блокировки одновременно на одном и том же промежутке?). И это главная причина тупика здесь - и то, и другое.
t2
пытается получить блокировку IX (вставить намерение) для пробела, а затем блокировку X для вставленной записи, но оба ждут друг друга из-за блокировки, полученной с помощью .
Эта проблема не возникает при использовании уровня изоляции транзакций.
SELECT ... FOR UPDATE
не удерживает никаких блокировок, если запись не найдена и
READ COMMITED
используется уровень изоляции. Так что первый добьется успеха. Второй будет заблокирован ЭКСКЛЮЗИВНОЙ блокировкой, полученной первым, и после того, как он будет зафиксирован, второй просто выбросит
Duplicate entry '1' for key 'PRIMARY'
.
Теперь можно подумать, что эта ситуация не лучше тупиковой. Просто еще одна ошибка. А теперь представьте, что секунда
INSERT
пытается вставить запись с
id=2
. В этом случае он не будет заблокирован
t1
и обе транзакции будут успешными. И это большая разница для некоторых приложений.