最近在做一个新项目,特点就是数据量特别大,更新还频繁,但没啥并发。照常写代码、写sql、测试、上线。。。上线之后便召唤来了质量同学。。。
亲,您的 bug 来了~
查一下居然是数据库死锁,业务就不介绍了,直接上死证吧
Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
于是赶快数据库执行下边的命令查一下为啥会有死锁,虽然有些时候可能通过报错的地方可以猜测出来。不过咱是个严谨的人儿~
------------------------
执行下边命令可以查看数据库最近的一次死锁,执行命令后,粘贴state里边的值就好了。中间部分就是死锁的内容,见下方
------------------------
show engine innodb status;
------------------------
LATEST DETECTED DEADLOCK (敏感内容咱已修改)
------------------------
2022-06-03 08:03:23 0x2b8a3af39700
*** (1) TRANSACTION:
// 注释:事务ID 355199151
TRANSACTION 355199151,
// 注释:当前事务活跃了 97S,且只使用一个表
ACTIVE 97 sec starting index read mysql tables in use 1,
// 注释:locked 1 表示有一个表锁;LOCK WAIT 表示正在等待锁;4 lock struct(s) 表示四个锁锁结构
locked 1 LOCK WAIT 4 lock struct(s),
heap size 1136,
2 row lock(s),
undo log entries 1 MySQL thread id 13767769,
OS thread handle 47871886231296,
query id 4662968700 127.0.0.1 u_user_rw updating
DELETE FROM user WHERE (UNIQUE_CODE IN ('xxxxx'))
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10900 page no 4213 n bits 192 index uq_unique_code of table `user_uat`.`user` trx id 355199151
lock_mode X locks rec but not gap waitingRecord lock, heap no 124 PHYSICAL RECORD: n_fields 2; compact format;
info bits 32
0: len 30; hex 313831313536343765326163613831663762343130376363613266323936; asc 18115647e2aca81f7b4107cca2f296; (total 64 bytes);
1: len 8; hex 80000000000558d4; asc X ;;
*** (2) TRANSACTION:
TRANSACTION 355199146, ACTIVE 99 sec inserting
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 3
MySQL thread id 13767760, OS thread handle 47872694523648, query id 4662969187 127.0.0.1 u_user_rw update
INSERT INTO user (xxx,xxx,xxx)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 10900 page no 4213 n bits 192 index uq_unique_code of table `user_uat`.`user` trx id 355199146
lock_mode X locks rec but not gap Record lock, heap no 124 PHYSICAL RECORD: n_fields 2; compact format;
info bits 32
0: len 30; hex 313831313536343765326163613831663762343130376363613266323936; asc 18115647e2aca81f7b4107cca2f296; (total 64 bytes);
1: len 8; hex 80000000000558d4; asc X ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10900 page no 4213 n bits 192 index uq_unique_code of table `user_uat`.`user` trx id 355199146
lock mode S waiting Record lock, heap no 124 PHYSICAL RECORD: n_fields 2; compact format;
info bits 32
0: len 30; hex 313831313536343765326163613831663762343130376363613266323936; asc 18115647e2aca81f7b4107cca2f296; (total 64 bytes);
1: len 8; hex 80000000000558d4; asc X ;;
*** WE ROLL BACK TRANSACTION (1)
具体处理大致就是 delete 是正常业务流程,update 是一个任务调度的异步批量操作。所以改 update 就完了,代码问题,具体怎么改咱还要脸就不说了
问题处理完了,看着东西不多。那些乱七八糟的锁看似眼熟,出了问题才发现谁也不认识谁。所以各种问度娘的问题还是要总结下的
首先锁可以分为:表锁,页锁,行锁
行锁之共享锁( S lock )
允许事务读一行数据,一般记为 S,也称为读锁
行锁之排他锁 ( X lock )
允许事务删除或者更新一行数据,一般记为 X,也称为写锁
| 兼容性 | X | S |
|---|---|---|
| X | 不兼容 | 不兼容 |
| S | 不兼容 | 兼容 |
Record Lock(记录锁)
这里需要明白的是,锁直接加在索引记录上,而不是行数据
Gap Lock(间隙锁)
这里需要明白的是,锁加在了索引记录间隙(记住是间隙不是记录本身!),确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别
什么是间隙?
假如有一个索引 key 目前有 (1, 3, 5, 7, 9) 五个 key。你要是更新 key=7 时。间隙锁就会锁定 (5,7) 和 (7,9) 这两个范围的数据,然后找到 key=7 的数据行的主键索引和非唯一索引,对 key 加上锁