建议手动复现一下,和只看是两码事、、、、、、、、、、、
1、问题描述
场景:订单支付成功后用订单号批量通知修改计息状态发生死锁
2、前提:数据库表订单号varchar类型唯一索引跟图中name字段相同
3、操作流程
update book set platform_version=400 where name=1111
由于修改订单为批量操作,并且为一条一条修改库表
4、分析:name为唯一索引应该锁行修改,为什么会发生死锁,猜想肯定是两个事物相互依赖导致
分析一:show engine innodb status;命令
第一部分,关键词是:
(1)Transaction 1,事务105528;
(2)在执行update book set platform_version=400 where name=2222
(3)正在等待锁释放(waiting for this lock to be granted),记录锁(record locks),主键索引上(index primary),互斥锁(lock_mode X),物理记录(physical record),asc 3333;
分析完这个最近一次死锁后还是有疑问:2222唯一索引干嘛还要拿3333的锁,闹腾、、、、、
分析二:explain小工具查看执行计划
select_type:UPDATE
这是一个简单类型的SQL语句,不含子查询或者UNION。
type:index
访问类型,即找到所需数据使用的遍历方式,潜在的方式有:
(1)ALL(Full Table Scan):全表扫描;
(2)index:走索引的全表扫描;
(3)range:命中where子句的范围索引扫描;
(4)ref/eq_ref:非唯一索引/唯一索引单值扫描;
(5)const/system:常量扫描;
(6)NULL:不用访问表;
这里为什么是index,唯一索引不应该是走单条吗,怎么会扫全表
key: PRIMARY
实际使用索引:这个是走的主键索引
ref:NULL
哪些列,或者常量用于查找索引上的值,我们条件name说明索引没好使、、、、
rows:7
找到这条数据预读了7行,,尴尬,这不正是全表吗
到这里很明显我们的name唯一索引并 没有起作用,为什么、、、、、
这个时候回到表结构name 是varchar类型,而sql
update boot set platform_version=400 where name=3333;
name用的number类型,,,
类型转换,导致锁升级,造成全表扫、、、
重新执行sql:
果然印证了猜想:
(1)type:range,变为了走索引的字符串比对,范围扫描;
(2)possible_keys:index_name,通过index_name索引找到了记录;
(3)key:index_name,实际使用index_name索引;
(4)ref:const,使用了常量' 3333'进行比对;
(5)rows:1,预估读取行数是1;
就本例而言:需要注意字符串与整数之间的强制类型转换,有时候少一个引号,就会使得行锁升级为表锁。
死锁是MySQL中非常难调试的问题,常见的思路与方法有:
(1)通过多终端模拟并发事务,复现死锁;
(2)通过show engine innodb status; 可以查看事务与锁的信息;
(3)通过explain可以查看执行计划
公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢