#mysql #transactions #locking #innodb
Вопрос:
Я запутался в том, как работают блокировки MySQL InnoDB.
Чтобы лучше понять это, я провел приведенный ниже эксперимент. Я использовал Spring boot и JPA для вставки/обновления записей в таблице ниже (хотя этот вопрос не связан с Spring boot или JPA).
Имя таблицы: test
Имя столбца | Тип | Ограничение |
---|---|---|
ID | Бигинт | PK AI |
Имя | Варчар | |
вэл | Инт | |
br_id | Инт |
Время ожидания блокировки Innodb: 50 секунд
Хранилище: ITestRepository.java
@Modifying @Query(value = "update test set val=val 1 where valgt;:val and br_id=:brId", nativeQuery = true) // Just a random update query public void updateVal(Integer val, Integer brId);
Обслуживание: TestService.java
@org.springframework.transaction.annotation.Transactional public void saveMany(final int brId) { // brId is always passed as 1 for (int i = 0; i lt; 200; i ) { this.testRepository.updateVal(i, brId); // ======gt; LINE: 1 this.testRepository.save(new TestEntity(String.valueOf(i), i, brId)); // ======gt; LINE: 2 try { Thread.sleep(2000); } catch (final InterruptedException e) { e.printStackTrace(); } } } @org.springframework.transaction.annotation.Transactional public void saveOne(final int val, final int brId) { // brId is always passed as 2 this.testRepository.updateVal(val, brId); // ======gt; LINE: 3 this.testRepository.save(new TestEntity(String.valueOf(val), val, brId)); // ======gt; LINE: 4 }
- ПРИМЕЧАНИЕ 1: Таблица всегда усекается перед выполнением любого из приведенных ниже случаев.
- ПРИМЕЧАНИЕ 2: Намеренно, что приведенный выше
update
запрос ничего не обновит.
Случай 1: Запуск saveMany
метода только один раз
В этом случае все работает нормально, и 200 строк вставляются после завершения транзакции без каких-либо ошибок.
Случай 2: saveMany
Сначала срабатывает, а затем saveOne
один раз
In this case, while saveMany
is being executed if we trigger saveOne
, saveOne
will fail with Lock wait timeout
exception after 50 sec, and saveMany
will successfully complete once the loop is over.
Case 3: Commenting LINE: 1
amp; LINE: 3
OR commenting LINE: 2
amp; LINE: 4
and Triggering saveMany
first, and then saveOne
once
In this case, while saveMany
is being executed if we trigger saveOne
, everything will work fine and both the methods will complete successfully without any exception.
Случай 4: Сначала комментируйте LINE: 1
и запускайте saveMany
, а затем saveOne
один раз
В этом случае во время saveMany
выполнения, если мы запустим saveOne
, saveOne
произойдет сбой LINE: 3
lock wait timeout
, за исключением операции обновления.
Случай 5: Сначала комментируйте LINE: 2
и запускайте saveMany
, а затем saveOne
один раз
В этом случае во время saveMany
выполнения, если мы запустим saveOne
, saveOne
произойдет сбой LINE: 4
lock wait timeout
, за исключением операции вставки.
Из приведенных выше случаев вот мои выводы:
- Параллельные
inserts
илиupdates
не блокирующие таблицы - Параллельно
insert
иupdate
заблокирует таблицу (какая бы операция не была запущена первой, она получит блокировку первой)
Я не в состоянии понять, как работают вышеуказанные два вывода. Я имею в виду, что из документации MySQL они утверждают, что вся таблица никогда не блокируется в InnoDB, и только строки блокируются при выполнении inserts
или updates
. Как вы можете видеть в приведенных выше случаях, br_id
всегда разные для обоих методов и, следовательно updates
, выполняются для разных наборов строк, тогда также почему lock wait timeout
возникает исключение? Кроме того, параллельны inserts
или updates
не вызывают никаких проблем, как и почему?
ПРАВКА 1:
Если br_id
не индексируется, то это работает так, как указано в приведенных выше случаях, но если br_id
индексируется, то Deadlock found when trying to get lock; try restarting transaction
исключение создается немедленно при выполнении saveOne
метода, параллельного saveMany
методу.
Комментарии:
1. Индексируются ли val и / или br_id?
2. @Тень Нет, это не так. Я уже упоминал все доступные ограничения в таблице.
3. Innodb блокирует записи, которые ему необходимо сканировать. Если доступного индекса нет, он может заблокировать все записи в таблице.
4. Так вы имеете в виду , что если я проиндексирую
br_id
, это не заблокирует таблицы? Позвольте мне попробовать.5. Но все же, почему параллельные
inserts
илиupdates
не вызывающие никаких проблем?
Ответ №1:
update test set val = ... where val gt; ... and br_id = ...
Поскольку в соответствующих столбцах нет индексов, все строки заблокированы.
Было бы лучше, если бы
INDEX(br_id)
или
INDEX(br_id, val)
Последнее лучше подходит для определения строк, которые необходимо изменить, но имеет недостаток, заключающийся в необходимости обновления BTree индекса.
Имейте в виду, что некоторые «замки» являются «замками зазора». Это включает в себя блокировку разрыва между индексированными значениями. Иногда это может вызвать сюрпризы. (Я не знаю, имеет ли это отношение к этому обновлению.)
Один из инструментов для просмотра блокировок SHOW ENGINE INNODB STATUS;
(поскольку информация там временная, она может не показывать то, что вам нужно).
BEGIN; UPDATE ... ((spend lots of time before COMMIT)) COMMIT;
Это либо задержит, либо приведет к блокировке любого конкурирующего соединения, для которого требуются одинаковые блокировки строк.
Почти всегда InnoDB может видеть, что просто останавливается (до innodb_lock_wait_timeout
нескольких секунд), чтобы получить необходимые блокировки.
InnoDB всегда может обнаружить тупиковую ситуацию и ROLLBACK
выполнит одну из транзакций, позволяя другой завершить.
Есть несколько случаев (особенно с «блокировкой пробелов»), когда InnoDB консервативно объявляет тупик, когда его могло и не быть. (Я не знаю, было ли это вашим делом.)
Комментарии:
1. Спасибо за ответ, Рик. Как я уже упоминал,
INDEX(br_id)
вызывает тупик (не знаю, как и почему). Такжеupdate
запрос построен таким образом, что он ничего не обновит в таблице. Также параллельupdates
или параллельinserts
не вызывают никаких проблем. Почему и как?2. @JigneshM.Khatri — С какой ценностью
autocommit
вы работаете? У вас есть явныеBEGIN...COMMIT
? Сколько времени проходит между Началом и фиксацией? Покажите нам SQL для инструкции Update. ПредоставлятьSHOW CREATE TABLE
.3. Из моего вопроса следует,
saveMany
что метод зафиксирует все 200insert
иupdate
операции сразу, через несколько секунд. Существует намеренная задержка в 2 секунды между каждойinsert-update
операциейsaveMany
. Как я уже сказал, SQL дляupdate
уже упоминается в вопросе. ВидетьITestRepository.java
.4. Существует сотня фреймворков, скрывающих SQL. Я могу давать советы по SQL, но я не могу изучить все сто фреймворков. Если вы предоставите сгенерированный SQL, я могу помочь в дальнейшем. В противном случае вам придется подождать, пока кто-то, кто знает как структуру, которую вы используете, так и детали MySQL, даст вам ответ. Предлагаю вам добавить [тег], чтобы помочь найти такого человека.
5. Чтобы на самом деле увидеть используемые в настоящее время блокировки, посмотрите таблицу performance_schema.data_locks.