前言:最近我的项目中的定时工作生产队列始终呈现一个反复的惟一主键谬误,是多个事务对同一行数据进行操作引起的。解决这个问题后,我便写了这篇博客的草稿,由ctx同学强势审核批改后,便有了这一版本的博客。
场景复现
出错办法:
private void handleAccountRisk(String accountUuid, float risk, RiskConfEtcdVo riskConf, Long currentTimeSeconds) { // 分布式锁 String redisKey = RISK_UPDATE_LOCK_KEY.replace("${item}", accountUuid); String businessId = BusinessIdGeneratorUtil.businessId(); redissonLockUtil.lock(redisKey,businessId, () -> { AccountRiskEntity accountRisk = accountRiskTplDao.getAccountRiskByUuid(accountUuid); // 如果accountRisk不存在则新增 if (null == accountRisk) { // 实例化一个对象后填充... // insert accountRiskTplDao.save(target); // 如果存在则更新 } else { ... accountRiskTplDao.updateById(accountRisk); ... } });}
报错信息:
INSERT INTO account_risk ( create_time, update_time, risk, uuid ) VALUES ( ?, ?, ?,com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'dc57ktzmtiwp' for key 'uuid'
问题剖析
从Duplicate entry推断出反复插入。具体起因为:
多个事务在执行这个办法。
事务A:依据uuid查不到数据,实例化一个实体,insert,但还未提交事务
事务B:依据uuid查不到数据,实例化一个实体
此时 :A提交了事务,B执行insert
事务B:insert中呈现报错:Duplicate entry 'dc57ktzmtiwp' for key 'uuid'
解决方案
依据下面的剖析,主须要对指定uuid对应的行数据加锁,不容许多个事务同时对该行记录进行操作。
能够通过应用innodb行级锁来解决。
getAccountRiskByUuid办法的sql前面加上for update
:
public AccountRiskEntity getAccountRiskByUuid(String uuid){ QueryWrapper<AccountRiskEntity> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("uuid",uuid); // 乐观锁 queryWrapper.last("FOR UPDATE"); return baseMapper.selectOne(queryWrapper);}
类似场景
DDIA的第七章事务中,对这种状况有具体的举例形容。
对于InnoDB行锁
加锁形式
- 共享锁(S):select * from table_name where ... lock in share mode;
- 排他锁(X):select * from table_name where ... for update;
应用场景
如果遇到存在高并发并且对于数据的准确性很有要求的场景,是须要理解和应用for update的。 比方波及到金钱、库存等。个别这些操作都是很长一串并且是开启事务的。如果库存刚开始读的时候是1,而立马另一个过程进行了update将库存更新为0了,而事务还没有完结,会将错的数据始终执行上来,就会有问题。所以须要for upate 进行数据加锁避免高并发时候数据出错。 记住一个准则:一锁二判三更新
InnoDb行锁的实现形式:
InnoDB行锁是通过给索引项加锁来实现的,如果没有索引,InnoDB将通过暗藏的聚簇索引来对记录桎梏。没有索引的话会进化成锁表!
幻读:指以后某个事务在读取某个范畴内的记录时,另外一个事务又在该范畴内插入了新的记录。当之前的事务再次读取该范畴的记录时,会产生幻行。---《高性能Mysql》
集体了解:当咱们根据(某个事务在读取某个范畴内的记录)的后果作为判断条件决定后续的操作,在另一个事务(又在该范畴内插入了新的记录)后,之前判断条件的后果就可能会被扭转了。违反了这个判断条件,那么后续的操作就须要放弃疑难了?