【发布时间】:2021-10-15 07:37:58
【问题描述】:
我对 MySQL InnoDB 锁的工作原理感到困惑。
为了更好地理解它,我进行了以下实验。我使用 Spring boot 和 JPA 在下表中插入/更新记录(尽管这个问题与 Spring boot 或 JPA 无关)。
表名: test
| Column name | Type | Constraint |
|---|---|---|
| id | Bigint | PK AI |
| name | Varchar | |
| val | Int | |
| br_id | Int |
Innodb 锁等待超时: 50 秒
存储库: ITestRepository.java
@Modifying
@Query(value = "update test set val=val+1 where val>: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 < 200; i++) {
this.testRepository.updateVal(i, brId); // ======> LINE: 1
this.testRepository.save(new TestEntity(String.valueOf(i), i, brId)); // ======> 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); // ======> LINE: 3
this.testRepository.save(new TestEntity(String.valueOf(val), val, brId)); // ======> LINE: 4
}
- 注意 1: 在执行以下任何情况之前,表格总是被截断。
-
注意 2: 上面的
update查询是故意不会更新任何内容的。
案例1: 只触发一次saveMany方法
在这种情况下,一切正常,事务完成后插入 200 行,没有任何错误。
案例2: 先触发saveMany,然后触发saveOne一次
在这种情况下,如果我们触发 saveOne,则在执行 saveMany 时,saveOne 将在 50 秒后失败并出现 Lock wait timeout 异常,并且一旦循环结束,saveMany 将成功完成。
案例 3: 评论 LINE: 1 & LINE: 3 或评论 LINE: 2 & LINE: 4 并首先触发 saveMany,然后 saveOne 一次
在这种情况下,如果我们触发saveOne,则会在执行saveMany 时,一切都会正常工作,两种方法都会成功完成,没有任何异常。
案例4: 先评论LINE: 1并触发saveMany,然后saveOne一次
在这种情况下,如果我们触发 saveOne,则在执行 saveMany 时,saveOne 将在 LINE: 3 上失败,lock wait timeout 更新操作异常。
案例5: 先评论LINE: 2并触发saveMany,然后再触发saveOne一次
在这种情况下,如果我们触发 saveOne,则在执行 saveMany 时,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方法。
【问题讨论】:
-
是否 val 和/或 br_id 已编入索引?
-
@Shadow 不,不是。我已经在表格中提到了所有可用的约束。
-
Innodb 锁定它需要扫描的记录。如果没有可用的索引,那么它可能会锁定表中的所有记录。
-
所以你的意思是如果我索引
br_id,它不会锁定表?让我试一试。 -
但是,为什么并行
inserts或updates没有引起任何问题?
标签: mysql transactions locking innodb