记一次线上死锁
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