唯一索引并发更新导致的死锁问题

在生产环境遇到签到接口并发问题,update语句和表结构如下:
update channel_token set token = ? where channel_code = ? and dot_code=? and version=?;
CREATE TABLE channel_token (
channel_code VARCHAR(32) NOT NULL ,
dot_code VARCHAR(32) NULL DEFAULT NULL ,
token VARCHAR(32) NOT NULL ,
type VARCHAR(1) NOT NULL ,
expire_time DATETIME NOT NULL ,
last_time DATETIME NOT NULL ,
version BIGINT(20) NOT NULL,
PRIMARY KEY (token),
UNIQUE INDEX uniqe_index (channel_code, dot_code) USING HASH
)COLLATE=‘utf8_general_ci’ ENGINE=InnoDB;

mysql的事务支持与存储引擎有关,MyISAM不支持事务,INNODB支持事务,更新时采用的是行级锁。这里采用的是INNODB做存储引擎,意味着会将update语句做为一个事务来处理。前面提到行级锁必须建立在索引的基础,这条更新语句用到了唯一索引,所以这里肯定会加上行级锁。 行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引
这个update语句会执行以下步骤:
1、由于用到了非主键索引,首先需要获取普通索引上的行级锁
2、紧接着根据主键进行更新,所以需要获取主键上的行级锁;
3、更新完毕后,提交,并释放所有锁。

innodb引擎会先判断插入的行是否产生重复key错误,如果存在,在对该现有的行加上S(共享锁)锁,如果返回该行数据给mysql,然后mysql执行完duplicate后的update操作,然后对该记录加上X(排他锁),最后进行update写入。

  1. 会话S1尝试获取记录的S锁,该记录未被其他会话加锁,获取S锁成功。
  2. 会话S2尝试获取记录的S锁,该记录上被加持S锁,但由于S锁与S锁兼容,获取S锁成功
  3. 会话S1尝试获取记录的X锁,由于会话S2对该记录持有S锁,S锁与X锁不兼容,获取X锁失败,会话S1被阻塞
  4. 会话S2尝试获取记录的X锁,由于会话S1对该记录持有S锁,S锁与X锁不兼容,获取X锁失败,会话S2被阻塞
  5. 会话S2被阻塞后进入死锁检查环节,发现阻塞S1->S2和S2->S1形成死锁环路,触发死锁机制强制回滚S1或S2事务。

经验教训:
无论前台后台的程序,都不应该存在仅根据非主键的几个字段一查就要update/delete的场景。即使有,也应该改为先把要更新的记录查出来然后逐条按主键id更新

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值