首先记住一个基本点,锁是对表的索引的节点进行锁,而不是对某行锁,当走不了索引时候可能就会锁表了。
这里我主要分析锁的模式。
记录锁
记录锁是最简单的行锁, 仅仅锁住一行 。即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。(record lock)
间隙锁(Gap Locks)
间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。主要对一个区间而加锁。关键字(gap before rec)
Next-Key Locks
Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。
插入意向锁(Insert Intention)
插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号。
锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):
实现死锁的步骤详细看下面的链接。
这里实现死锁之前的sql。
使用 show engine innodb status 查看死锁日志。
如何实现死锁,详细可以看链接文章,由于时间关系借了其的死锁日志简单分析。
(“//”则是自己转述的话)
2020-04-11 00:35:55 0x243c
*** (1) TRANSACTION: //表示第一个事务
TRANSACTION 38048, ACTIVE 92 sec inserting(2020-04-11 00:35:55 0x243c
*** (1) TRANSACTION:
TRANSACTION 38048, ACTIVE 92 sec inserting //这个事务执行插入sql
mysql tables in use 1, locked 1 //这个表存在锁
LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2 //正在等待锁
MySQL thread id 53, OS thread handle 2300, query id 2362 localhost ::1 root update
insert into account values(null,'Jay',100)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: //正在等待锁被释放
RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` //希望锁的索引
trx id 38048 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 //这个比较重要大概意思是 准备插入但是正在等待获取获取意向插入锁
//下面则是表示 这个锁会锁的范围
//重点留有这个范围 标记为 A
0: len 3; hex 576569; asc Wei;;(不包含Wei)
1: len 4; hex 80000002; asc ;;(未知)
*** (2) TRANSACTION: //表示第二个事务
TRANSACTION 38049, ACTIVE 72 sec inserting, thread declared inside InnoDB 5000 //这个事务执行插入sql
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 52, OS thread handle 9276, query id 2363 localhost ::1 root update
insert into account values(null,'Yan',100)
*** (2) HOLDS THE LOCK(S): //重要点 这个事务锁上了一个锁
RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` //这个锁 锁的索引
trx id 38049 lock_mode X locks gap before rec //表示上了一个排他间隙锁
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
//这个锁 锁的索引的详细的节点范围
//重点留有这个范围 标记为 B ,可见和A的存在交集,则会冲突
0: len 3; hex 576569; asc Wei;;(不包含Wei)
1: len 4; hex 80000002; asc ;;(未知)
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: //同是第二个事务 等待锁
RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` //希望锁的索引
trx id 38049 lock_mode X insert intention waiting //等待插入意向锁
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
//等待的锁的索引范围 可见和A的存在交集,则会冲突
0: len 8; hex 73757072656d756d; asc supremum;; (无限)
事务B执行更新时候 会锁了部分节点的索引,然后 事务A 执行插入 数据的索引节点刚刚好和事务B的持有的索引锁冲突了,A则等待B释放,然后B又则行了insert的索引节点需要的锁和A希望持有的冲突的了。锁会排队,先申请先获取的,所以就会 A等B B等A的情况出现死锁。
我认为 可以先判断锁之间的互斥关系,然后互斥,则判断其锁的范围,如果还是冲突,则会死锁了。
出现死锁,一般都会设置超时的时间的,也可以通过命令是用来查看当前运行的所有事务:
SELECT * FROM information_schema.INNODB_TRX;
然后 killtrx_mysql_thread_id
再排除出现死锁的操作,从而优化代码架构。
简单的建议:
高并发前提对操作相关数据源加上锁,常见是分布式锁,for update。
尽量减少大事务。
单个事务执行时间尽量缩短。
感谢参考链接:
https://www.tuicool.com/articles/zEvU3me