mysql - 了解 MySQL InnoDB 锁
问题描述
我对 MySQL InnoDB 锁的工作方式感到困惑。
为了更好地理解它,我进行了以下实验。我已经使用 Spring boot 和 JPA 在下表中插入/更新记录(尽管这个问题与 Spring boot 或 JPA 无关)。
表名: test
列名 | 类型 | 约束 |
---|---|---|
ID | 比格特 | PK人工智能 |
姓名 | 瓦尔查尔 | |
值 | 诠释 | |
br_id | 诠释 |
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
一次
在这种情况下,如果我们触发 whilesaveMany
正在执行saveOne
,将在 50 秒后因异常saveOne
而失败,并在循环结束后成功完成。Lock wait timeout
saveMany
案例 3:先 评论LINE: 1
& LINE: 3
OR 评论LINE: 2
&LINE: 4
和触发saveMany
,然后再触发saveOne
一次
在这种情况下,saveMany
如果我们 trigger 正在执行saveOne
,一切都会正常工作,并且这两个方法都会成功完成,没有任何异常。
案例四:先 评论LINE: 1
触发saveMany
,再评论saveOne
一次
在这种情况下,如果我们触发, whilesaveMany
正在执行saveOne
,saveOne
将失败并出现LINE: 3
更新lock wait timeout
操作异常。
案例5:先 评论LINE: 2
触发saveMany
,再评论saveOne
一次
在这种情况下,如果我们触发, whilesaveMany
正在执行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
解决方案
update test
set val = ...
where val > ...
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 在可能没有死锁时保守地声明死锁。(我不知道这是不是你的情况。)
推荐阅读
- android - 搜索过滤器不在列表视图中显示搜索到的项目
- leaflet - 单击标记后,地图如何以移动标记为中心?
- php - 如何在论坛中增加以下视图
- javascript - 使用 Chrome 扩展(特别是)修改 Twitch 剪辑 DOM
- c++ - 一段时间后 SolvePNP 返回错误的 rvec 和 tvec
- azure - Azure 存储帐户与存储作为 RG 中的资源
- django-models - Django:如何在两个数据库表中搜索,或者两个如何合并两个相关的类
- go - 检查符文是否在基本多语言平面中的正确方法是什么?
- aurelia - 父组件的 Aurelia 火灾验证
- java - 我们可以使用新的 REST 客户端将 Java Pojo 传递给 Elasticsearch Index API