由index_merge引发的死锁事件

问题:最近出现大量如下报错

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.Util.getInstance(Util.java:386)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2719)
    at com.mysql.jdbc.PreparedStatement.executeInternal(Prepa

有没有发现每次mysql报的所有死锁都是类似这个的问题,其实我们根据他只能判断出是哪个sql出问题了,但是并不知道为啥出问题了,那么就看看 出问题的sql:

update f_n_order_info
        set last_updated = now(),root_id = id
        where order_no = #{orderNo}
        and last_updated = #{lastUpdated}

sql特征:
order_no:唯一索引
last_updated:普通索引
初判:
以为逻辑是事务范围太大,所以决定缩小事务范围
推翻:
既然事务范围太大,逻辑中还有insert,update,query为啥只有这这个update语句总是死锁,说不通
再判:
sql语句中where后面有两个索引做为条件,于是找到度娘
不恰当的update语句使用主键和索引导致mysql死锁

以为是update语句中where条件用了主键索引和主键索引,行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引,但是我们的sql中存在只有主键索引的update语句,如果先执行了这种update语句就会使另一个sql语句无法获取到锁。
以及接近事实,但是还不是事实,原因是我们的实际场景中并没有出现博客中说的,所以又pass了,下面就要揭开答案的真相了。
终判:
从mysql的日志来看:
这里写图片描述
看到加重地方的提示事务2没有获取到gap锁,其实我不明白什么是间隙锁,先普及一下:
如下图所示,事务A在第一次查询时得到1条记录,在第二次执行相同查询时却得到两条记录。从事务A角度上看是见鬼了!这里写图片描述
这就是幻读,RC级别下尽管加了行锁,但还是避免不了幻读。
innodb的RR隔离级别可以避免幻读发生,怎么实现?当然需要借助于锁了!
为了解决幻读问题,innodb引入了gap锁。
在事务A执行:

update msg set message=‘订单’ where token=‘asd’;

innodb首先会和RC级别一样,给索引上的记录添加上X锁,此外,还在非唯一索引’asd’与相邻两个索引的区间加上锁。
这样,当事务B在执行

insert into msg values (null,‘asd',’hello’); commit;

时,会首先检查这个区间是否被锁上,如果被锁上,则不能立即执行,需要等待该gap锁被释放。这样就能避免幻读问题。这里写图片描述

这里写图片描述
这个图是其实是说事务1和事务2中的两个索引互相持有锁

解决方案:
1、关掉mysql的index_merge
2、
update f_n_order_info
set last_updated = now(),root_id = id
where order_no = #{orderNo}
and last_updated = #{lastUpdated}

将order_no改为id,因为id不存在gap锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值