记一次线上死锁

2018-12-21  本文已影响13人  史小猿

项目做了什么

这个项目是为了处理一些任务,实现是启动定时任务去处理这些数据,因为项目上线后是集群的,所以为了避免多个节点处理到相同的数据,我们使用select ... where state =0 for update limit 500 来锁住数据,然后update state =2值。这样其他节点就不会重复处理数据了

同事定时任务的代码大概是这样的,我这里用伪代码表示

@Transaction //注意使用for update一定要在事务里边
processData(){
bikeIds = findForUpdate();//select * from test_rawdata where state=0 for update limit 500;
//取出数据,然后先状态改成2
updateData(bikeIds);//update test_rawdata set state = 2 where bikeId in(bikeIds);

}

除了这个地方操作db外,其他地方也有insert delete,所以for update的where条件里必须要有索引,因为如果where 里的字段没有索引的话,for update就不是行锁了,就升级成表锁了。
那当处理定时任务时,insert和delete都将会被阻塞,这样并发性太弱了。

CREATE TABLE `test_rawdata` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `bikeId` varchar(15) NOT NULL COMMENT '车id',
  `lat` decimal(11,8) NOT NULL DEFAULT '0.00000000' COMMENT '纬度',
  `lng` decimal(11,8) NOT NULL DEFAULT '0.00000000' COMMENT '经度',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建更新时间,默认当前时间',
  `state` int(8) NOT NULL DEFAULT '0' COMMENT '0表示未处理 1处理中',
  `accuracy` smallint(6) NOT NULL DEFAULT '0' COMMENT '位置精确度',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_bikeid` (`bikeId`),
  KEY `idx_time` (`update_time`) USING BTREE,
  KEY `idx_state` (`state`)
) ENGINE=InnoDB AUTO_INCREMENT=8897633 DEFAULT CHARSET=utf8 COMMENT='车辆原始位置';
db记录

trx1

set autocommit = 0;
begin;
select * from test_rawdata where state=0 for update;
select sleep(8);
update test_rawdata set state = 2 where bikeId = "7fd9gSBkZ1";
commit;

trx

set autocommit = 0;
begin;
delete from test_rawdata where bikeId = '7fd9gSBkZ1'; //在trx sleep时间点执行
commit;

发生死锁
死锁日志


参考链接
innodb的锁 https://zhuanlan.zhihu.com/p/31875702
索引和锁 https://zhuanlan.zhihu.com/p/40396971
https://www.jianshu.com/p/1dc4250c6f6f

上一篇下一篇

猜你喜欢

热点阅读