Почему я не должен использовать "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и обе транзакции будут успешными. И это большая разница для некоторых приложений.

Другие вопросы по тегам