一 前言 死锁是每个MySQL DBA 都会遇到的技术问题,本文是自己针对死锁学习的一个总结,了解死锁是什么,MySQL如何检测死锁,处理死锁,死锁的案例,如何避免死锁。
二 死锁是什么 死锁 是并发系统中常见的问题,同样也会出现在Innodb系统中。当两个及以上的事务,双方都在等待对方释放已经持有的锁或者因为加锁顺序不一致造成循环等待锁资源,就会出现"死锁"。举例来说A 事务持有x1锁 ,申请x2锁,B 事务持有x2锁,申请x1 锁。A和B 事务持有锁并且申请对方持有的锁进入循环等待,就造成死锁。
从死锁的定义来看,MySQL出现死锁的几个要素:
三 MySQL的死锁机制 死锁机制包含两部分:检测和处理。 把事务等待列表和锁等待信息列表通过事务信息进行wait-for graph 检测,如果发现有闭环,则回滚undo log 量少的事务;死锁检测本身也会算检测本身所需要的成本,以便应对检测超时导致的意外情况。
3.1 死锁检测 当InnoDB事务尝试获取(请求)加一个锁,并且需要等待时,InnoDB 会进行死锁检测. 正常的流程如下:
3.2 如何处理死锁 《数据库系统实现》里面提到的死锁处理
四 Innodb 的锁类型 首先我们要知道对于MySQL有两种常规锁模式
最容易理解的锁模式,读加共享锁(in share mode),写加排它锁. 有如下几种锁的属性
锁的属性可以与锁模式任意组合。例如.
注 关于Innodb 锁的详细介绍 可以移步 官方文档 或者 MySQL · 引擎特性 · InnoDB 事务锁系统简介 五 Innodb 不同事务加锁类型 实例 update tab set x=1 where id= 1 ; 1. 索引列是主键,RC隔离级别 对记录记录加X锁 2. 索引列是二级唯一索引,RC隔离级别 若id列是unique列,其上有unique索引。那么SQL需要加两个X锁,一个对应于id unique索引上的id = 10的记录,另一把锁对应于聚簇索引上的[name=’d’,id=10]的记录。 3. 索引列是二级非唯一索引,RC隔离级别 若id列上有非唯一索引,那么对应的所有满足SQL查询条件的记录,都会被加锁。同时,这些记录在主键索引上的记录,也会被加锁。 4. 索引列上没有索引,RC隔离级别 若id列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。 5. 索引列是主键,RR隔离级别 对记录记录加X锁。 6. 索引列是二级唯一索引,RR隔离级别 对表加上两个X锁,唯一索引满足条件的记录上一个,对应的聚簇索引(主键)上的记录一个。 7. 索引列是二级非唯一索引,RR隔离级别 结论:Repeatable Read隔离级别下,id列上有一个非唯一索引,对应SQL:delete from t1 where id = 10; 首先,通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。 8 索引列上没有索引,RR隔离级别 则锁全表. 这里需要重点说明insert 和delete的加锁方式,因为目前遇到的大部分案例或者部分难以分析的案例都是和delete,insert 操作有关。 insert 的加锁方式 对于并发insert造成唯一键冲突的时候 insert的加锁策略是
引自 死锁案例之三 delete 的加锁方式
引自文章 MySQL DELETE 删除语句加锁分析
六 如何查看死锁
1. 查看事务锁等待状态情况
select * from information_schema.innodb_locks; select * from information_schema.innodb_lock_waits; select * from information_schema.innodb_trx;
下面的查询可以得到当前状况下数据库的等待情况:via《innodb技术内幕中》
2. 打开下列参数,获取更详细的事务和死锁信息。 innodb_print_all_deadlocks = ON innodb_status_output_locks = ON 3. 查看innodb状态(包含最近的死锁日志) show engine innodb status;
4 工欲善其事,必先利其器: 如何阅读死锁日志
七 死锁案例
关于死锁的案例不在本文做详细分析,这里给出我做的几个例子(后续会有其他案例分享),四个案例基本均和RR 模式下的gap锁有关。 死锁案例之一 delete申请gap锁与insert 的gap锁冲突导致死锁 死锁案例之二 并发delete不存在记录申请gap锁导致死锁 死锁案例之三 两个事务并发insert 唯一键冲突 和gap锁一起导致的死锁案例 死锁案例之四 三个并发insert 语句导致的死锁
八 如何尽可能避免死锁 1. 事务隔离级别使用read committed和binlog_format=row ,避免RR模式带来的gap锁竞争。 2. 合理的设计索引,区分度高的列放到组合索引前列,使业务sql尽可能的通过索引定位更少的行,减少锁竞争。 3. 调整业务逻辑 SQL执行顺序,避免update/delete 长时间持有锁sql在事务前面,(该优化视情况而定) 4. 选择合理的事务大小,小事务发生锁冲突的几率也更小; 5. 访问相同的表时,应尽量约定以相同的顺序访问表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会; 6. 5.7.15 版本之后提供了新的功能 innodb_deadlock_detect 参数,可以关闭死锁检测,提高并发TPS。 参考文章 1 官方文档 2 MySQL · 引擎特性 · InnoDB 事务锁系统简介 3 mysql insert锁机制 4 MySQL 加锁分析 5 一个最不可思议的MySQL死锁分析 6 谈谈MySQL死锁 一 7 谈谈MySQL死锁之二 死锁检测和处理源码分析