Deadlock
关于死锁
在做认列项目时,由于用到了多进程,在高并发场景下,出现了死锁。
死锁并不可怕,可以用来查看最近一次死锁记录,分析找出原因。
PS: 更为妥当的做法是纪录到日志文件,5.6以上只要打开innodb_print_all_deadlocks配置即可,5.5则需要使用,percona-toolkit提供了的脚本pt-deadlock-logger实现
锁是解决多线程资源竞争的一种方法,mysql的锁就是解决事务间对数据检索更新的竞争,保证数据一致性的重要手段。
此前,我们要先了解innodb的锁
PS: 还有IX 和IS 这里为了方便说明,简化了锁
X:排他锁(读写锁)
更新、删除数据时用,阻塞其他线程对数据的读写操作
S:共享锁(写锁)
读取数据时用,阻塞写操作
所以有了很多文章里都提到的锁的相容性:
两个线程同时获取共享锁是相容的,不会出现锁的竞争;
只有有一个线程获取排他锁,就会与另外一个线程的锁冲突,产生竞争,从而有机会产生死锁。
下面看认列项目触发的死锁例子
在上面的日志我们可以看到,首行有具体的日期
接下来就是案发现场
我们首先来关注
这两行就是两个事务涉及到的SQL,正是这两个SQL引发了惨案,这里有助与我们定位程式的位置
再来看
这两行说明了两个事务锁住的行数
undo log entries 3 表示事务#2有将三行纪录到了回滚队列
接着看
重点在RECORD LOCKS… 三行
RECORD LOCKS 表明是行锁,锁住的是索引Indexof table.指出具体锁住或等待锁的索引是哪一个,属于哪个表
trx id 是事务的ID,事务ID说明了事务的执行顺序
lock_mode 表示锁的类型,X 排他锁,S 共享锁,还有IX,IS意向锁
好了,解释这么多,现在我们可以来重现案件
1.事务#2执行更新SQL,并获取了索引revenue_summary_status_index的行锁
2. 事务#2 还没提交,事务#1 执行了新的SQL,并申请了revenue_summary_status_index 的行锁,再等待事务#2 的锁释放
解决死锁的思路,就是降低锁的颗粒度。
本次问题是索引锁,通过降低索引数据颗粒度的方式解决,建立一个联合索引(user_id, status)。
数据颗粒度可以使用 Explain 命令验证 SQL 的扫描行数,行数越少,颗粒度越少,并发性能越高。
根据日志来看,死锁的危害就是数据一致性被破坏,获取了低权重锁的事务被回滚。
譬如并发提交订单,会出现某些订单被回滚,没有执行成功,造成会员订单无法正常开启
再譬如在我们8891使用的搜寻临时表,批量更新物件数据时,也会出现。
所以偶尔有会员反应,他的物件没有被及时更新,需要再做修改才会更新。
要解决死锁危害:降低锁粒度,捕捉死锁异常
需要尽量在重要流程使用显式事务,避免被数据逻辑被破坏,捕捉事务异常,对异常进行逻辑处理
增加必要的索引,定期检查慢速、死锁日志,针对业务的 SQL 进行索引优化
结语:
本文更多的是介绍如何通过理解死锁日志,针对性的解决问题。更多死锁、innodb 特性的细节可以查看参考文章,也欢迎大家留言讨论、指正。
我们大部分场景都处在IO密集型,所以大部分性能瓶颈,也都在IO、DB这些较后的服务,然后才是代码层。定期的对IO、DB优化,可以解决大部分的性能问题。
参考:
https://dev.mysql.com/doc/refman/5.5/en/innodb-locking.html
https://dev.mysql.com/doc/refman/5.5/en/innodb-deadlocks.html
https://www.percona.com/blog/2014/10/28/how-to-deal-with-mysql-deadlocks/
https://dba.stackexchange.com/questions/116113/meaning-of-locks-rec-but-not-gap-waiting-in-deadlock-report
http://mysql.taobao.org/monthly/2015/07/05/
https://www.packtpub.com/books/content/optimizing-your-mysql-servers-performance-using-indexes
https://www.alibabacloud.com/forum/read-515
领取专属 10元无门槛券
私享最新 技术干货