场景形容:对立登录平台之前的操作步骤(用户利用绑定,用户角色绑定)比拟多,为晋升用户体验,对立批量操作,起初就加了一个用户组的概念(用户绑定用户组和角色,用户组绑定多个角色,角色绑定多个利用,这里波及到一些交并集操作),然而随之而来带来了一个问题,因为该零碎其实次要是面对B端用户,并发其实不高,很难遇到一些并发导致的问题,然而因为这个批量操作导致的大事务,事务工夫过长导致锁抵触的概率极大减少,所以就呈现了死锁问题。
而后我就查mysql死锁日志,show engine innodb status,该命令显示的内容长度有限度,能够设置SET GLOBAL innodb_status_output=ON;SET GLOBAL innodb_status_output_locks=ON;,默认输入到log-err,能够通过show variables like 'log_err%';查看设置的输入门路,如果是stderr,则示意规范谬误输入
而后我就看日志,发现了两个抵触的事务产生了死锁,次要是两张表,一张用户表open_app_user,主键id,执行的sql是update open_app_user
set xx=xx where id = 14310 and version
=5,另一张表是open_role_user,是用户和角色的关联表,就两个字段同时也是联结主键,role_id和user_id,执行的sql是delete from open_role_user
where user_id
=654321;
这里的delete from open_role_user
where user_id
= xx的业务逻辑是用户角色关系更新(可能是绑定或解绑),所以每个中央都须要依据user_id删除原来的记录再去新增,而这条sql是有问题的,这里须要阐明一下mysql的删除以及删除的加锁机制:
InnoDB上删除一条记录,并不是真正意义上的物理删除,而是将记录标识为删除状态(删除状态的记录会在索引中寄存一段时间)。所以在RR隔离级别下,1、在非惟一索引的状况下,删除一条存在的记录是有gap锁,锁住记录自身和记录之前的gap,InnoDB会在记录上加next key锁(对记录自身加X锁,同时锁住记录前的GAP,避免新的满足条件的记录插入。2、在惟一索引和主键的状况下删除一条存在的记录,因为都是惟一值,进行删除的时候,是不会有gap存在
以下是一段演示代码
//先执行删除的操作begin;delete from `open_role_user` where `user_id`=654321;
//再执行以下语句查看mysql的加锁状况select * from performance_schema.data_locks;
咱们看到删除应用非主键或惟一索引,mysql会加supremum pseudo-record一个gap锁和目前表里所有的记录都加一个record行锁
而后我换一条应用主键删除的sql:delete from open_role_user
where role_id = 654321 and user_id
=654321;
这时候就只加了一个record行锁了
对于应用非惟一索引或主键删除会相当于给整个表上锁,这时候抵触的概率是十分大的,而换成应用主键删除的话就只会锁住一行记录,这样是能无效缩小抵触状况的。
另外还有一种方法,就是把mysql默认的隔离级别RR可反复读改为RC读已提交,这次我执行
//先执行删除的操作begin;set session transaction isolation level read committed;delete from `open_role_user` where `user_id`=654321;
而后这边加锁就只会加上符号where条件的记录的锁了,实际上许多大公司也都是用的RC级别,包含oracle默认的就是RC隔离级别,相比于RR,对于UPDATE or DELETE语句,RC只为它更新或删除的行(合乎where条件的记录)持有锁,大大降低了死锁的可能性,对高并发场景比拟敌对。对于UPDATE,如果行已被锁定,InnoDB 还会执行一个“半统一读”,会多进行一次判断,当 where 条件匹配到的记录与以后持有锁的事务中的记录不抵触时,就会提前开释 InnoDB 锁,尽管这样做违反了二阶段加锁协定,但却能够缩小锁抵触,进步事务并发能力,是一种很好的优化行为。