作者:微信小助手
发布时间:2021-08-19T18:50:04
见字如面,我是码哥 咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍,对常见的死锁案例进行相关分析与探讨,以及如何去尽可能避免死锁给出一些建议。 死锁是并发系统中常见的问题,同样也会出现在数据库 MySQL 的并发读写请求场景中。 当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出现“死锁”。 常见的报错信息为 举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B 事务持有 X2 锁,申请 X1 锁。 A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。 如上图,是右侧的四辆汽车资源请求产生了回路现象,即死循环,导致了死锁。 从死锁的定义来看,MySQL 出现死锁的几个要素为: 为了分析死锁,我们有必要对 InnoDB 的锁类型有一个了解。 MySQL InnoDB 引擎实现了标准的 如果事务 T1 持有行 r 的 S 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理: 如果 T1 持有 r 的 X 锁,那么 T2 请求 r 的 X、S 锁都不能被立即允许,T2 必须等待 T1 释放 X 锁才可以,因为 X 锁与任何的锁都不兼容。 共享锁和排他锁的兼容性如下所示: 间隙锁锁住一个间隙以防止插入。假设索引列有 2, 4, 8 三个值,如果对 4 加锁,那么也会同时对(2,4)和(4,8)这两个间隙加锁。 其他事务无法插入索引值在这两个间隙之间的记录。但是,间隙锁有个例外: next-key lock 实际上就是 行锁+这条记录前面的 gap lock 的组合。假设有索引值 10,11,13 和 20,那么可能的 next-key lock 包括: 在 RR 隔离级别下,InnoDB 使用 next-key lock 主要是防止 InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB 支持了额外的一种锁方式,称之为意向锁( Intention Lock )。 意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。意向锁分为两种: 由于 InnoDB 存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。表级意向锁与行级锁的兼容性如下所示: 插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。 假设某列有索引值 2,6,只要两个事务插入位置不同(如事务 A 插入 3,事务 B 插入 4),那么就可以同时插入。 横向是已持有锁,纵向是正在请求的锁: 在进行具体案例分析之前,咱们先了解下如何去读懂死锁日志,尽可能地使用死锁日志里面的信息来帮助我们来解决死锁问题。 后面测试用例的数据库场景如下: 表结构和数据如下: 测试用例如下: 通过执行 show engine innodb status 可以查看到最近一次死锁的日志。 事务号为 2322,活跃 6 秒,starting index read 表示事务状态为根据索引读取数据。 常见的其他状态有: 本用例中 2locks 表示 IX 锁和 lock_mode X (Next-key lock) RECORD LOCKS 表示记录锁, 此条内容表示事务 1 正在等待表 student 上的 idx_stuno 的 X 锁,本案例中其实是 Next-Key Lock 。 事务 2 的 log 和上面分析类似: 显示事务 2 的 这点也是造成 DBA 仅仅根据日志难以分析死锁的问题的根本原因。 表示事务 2 的 insert 语句正在等待插入意向锁 表结构和数据如下所示: 测试用例如下: 日志分析如下: 表结构如下,无数据: 测试用例如下: 死锁分析: 可以看到两个事务 update 不存在的记录,先后获得 两者都持有 gap 锁,然后去竞争插入 好了。今天就说到这了,我还会不断分享自己的所学所想,希望我们一起走在成功的道路上! 欢迎大家留言讨论。
❝
什么是死锁
Deadlock found when trying to get lock...
。
InnoDB 锁类型
行级别锁:共享锁( S lock ) 和排他锁 ( X lock )
❝
❝
间隙锁( gap lock )
❝
next-key lock
(负无穷,10],(10,11],(11,13],(13,20],(20,正无穷)
幻读
问题产生。意向锁( Intention lock )
❝
插入意向锁( Insert Intention lock )
锁模式兼容矩阵
阅读死锁日志
MySQL 5.7 事务隔离级别为 RR
日志分析如下:
TRANSACTION 2322, ACTIVE 6 sec starting index read
mysql tables in use 1
说明当前的事务使用一个表。locked 1
表示表上有一个表锁,对于 DML 语句为 LOCK_IXLOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
LOCK WAIT
表示正在等待锁,2 lock struct(s)
表示 trx->trx_locks 锁链表的长度为 2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及自增锁等。1 row lock(s)
表示当前事务持有的行记录锁/ gap 锁的个数。MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating
MySQL thread id 37
表示执行该事务的线程 ID 为 37 (即 show processlist; 展示的 ID )delete from student where stuno=5
表示事务 1 正在执行的 sql,比较难受的事情是 show engine innodb status
是查看不到完整的 sql 的,通常显示当前正在等待锁的 sql。
RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw student trx id 2322 lock_mode X waiting
RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X
insert into student(stuno,score) values(2,10) 持有了 a=5 的 Lock mode X | LOCK_gap
,不过我们从日志里面看不到事务 2 执行的 delete from student where stuno=5
;
RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X locks gap before rec insert intention waiting
lock_mode X locks gap before rec insert intention waiting (LOCK_X+LOCK_REC_gap)
经典案例分析
案例一:事务并发 insert 唯一键冲突
排他行锁( Xlocks rec but no gap )
间隙锁
会申请锁住(,10],(10,20]之间的 gap 区域。
gap 锁4-10之间
, 故需事务 T2 的第二条 insert 语句要等待事务 T1 的
S-Next-key Lock 锁
释放,在日志中显示 lock_mode X locks gap before rec insert intention waiting 。
案例一:先 update 再 insert 的并发死锁问题
间隙锁( gap 锁)
,gap 锁之间是兼容的所以在 update 环节不会阻塞。意向锁
。当存在其他会话持有 gap 锁的时候,当前会话申请不了插入意向锁,导致死锁。如何尽可能避免死锁
定位更少的行,减少锁竞争
。
大事务
,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小。
固定的顺序
访问表和行。比如两个更新数据的事务,事务 A 更新数据的顺序为 1,2;事务 B 更新数据的顺序为 2,1。这样更可能会造成死锁。
(运行了 start transaction 或设置了autocommit 等于0)
,那么就会锁定所查找到的记录。
主键/索引
去查找记录,范围查找增加了锁冲突的可能性,也不要利用数据库做一些额外额度计算工作。比如有的程序会用到 “select … where … order by rand();”这样的语句,由于类似这样的语句用不到索引,因此将导致整个表的数据都被锁住。
减少连接的表
,将复杂 SQL
分解
为多个简单的 SQL。