业务背景:项目中包含历史版本的管理,每次保存历史版本都添加一条新的记录到数据库,且版本号加1。保存版本的时候1.要先查询历史版本数据表,获取当前最新的版本号;2.最新的版本号加1,插入数据历史版本记录表;3.备份一份当前最新的业务内容到业务表。 采用了事务管理@Transactional注解,确保历史版本的正确生成,避免出现版本记录了,但是内容未保存成功的现象。
业务问题:当同一个用户频繁点击多次保存历史版本,出现事务并发,导致出现插入两个相同的历史版本号。这个问题可以由前端控制,不能频繁点击,需等上一次点击返回结果,才可以点击下一次来避免。但是如果这个由后端处理该怎么解决呢?于是开始深入了解一下事务以及MVCC吧。
目录
首先事务四大特性:
事务并发引发的一致性问题:
事务的四种隔离级别:
MVCC多版本并发控制:
首先事务四大特性:
- 原子性:是指事务必须是一个原子的操作序列单元;
- 一致性:一个事务在执行之前和执行之后,数据库都必须处以一致性状态;
- 隔离性:是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰;
- 持久性:是指事务一旦提交后,数据库中的数据必须被永久的保存下来。
事务并发引发的一致性问题:
- 脏写:如果一个事务修改了另一个未提交事务修改过的数据;
- 脏读:一个事务读取了另一个未提交事务修改过的数据;
- 不可重复读:一个事务按照某个相同的搜索条件再次读取时得到了和第一次读取时不同的值;
- 幻读:一个事务按照某个相同的搜索条件多次读取时,在后续读到了之前没有的记录;
事务的四种隔离级别:
- READ UNCOMMITTED:读未提交,可能发生脏读、不可重复读、幻读现象;
- READ COMMITTED:读已提交,可能发生不可重复读、幻读现象,解决了脏读的发生;
- REPEATABLE READ:可重复读,可能发生幻读现象,但是不可能发生脏读和不可重复读读现象。(默认的,它利用MVCC解决了脏读、不可重复读、幻读等隔离问题,但是不能解决丢失更新问题。)
- SERIALIZABLE:可串行化,上述各种现象都不可能发生;
MVCC多版本并发控制:
是一种用来解决读写冲突的无锁并发控制,可以解决并发读写时,互不影响,提高并发读写性能。同时还解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。
MVCC实现原理是基于聚簇索引中的三个隐藏字段(trx_id,roll_pointer,row_id)、undo日志和readview来实现。
每个事务的id记录在trx_id,roll_pointer指向下一条undo日志,undo日志用来存放改动。rollpointer将改动链成一个版本链。readview用来判断哪个版本对当前事务可见。
以上是对事务的一个总结,那么我实际的业务问题属于什么问题呢?
一个用户同时发起两个版本添加的请求,事务A读取到当前最新版本是2,因为事务A还未提交,所以事务B读取当前最新版本也是2。事务A添加一条最新的记录版本3,事务B也添加一条最新的记录版本3.从而导致最终出现两条版本3的记录,这就相当于事务B的更新结果不正确,应该是版本4或者直接回滚。
出现该问题后,想了很多的解决方案,
方案一:是把隔离级别提高到SERIALIZABLE,让事务串行执行,这样确实只会插入一条记录,第二条的时候会报错出现死锁,从而导致回滚。因为事务A在执行锁定。但是后面查资料一般的话,生产环境不建议使用该方案,并发性差。
方案二:查询时添加for update 行级所
Example example = new Example(HistoryPo.class);
example.createCriteria().andEqualTo("Id", Id);
example.setOrderByClause("createTime desc");
example.isForUpdate();
List<HistoryPo> histories = historyPoMapper.selectByExample(example);
该方式无效,因为我的业务不是更新该行数据,而是根据该行数据查询到的版本号,新增一条记录。
还是插入了两条一样的数据
方案三:还考虑了一种方案,查询和插入放在一起
insert into history (ProjectId,Version)
select ProjectId, max(Version)+1 from history where ProjectId="xxxxxx"
不过这个也只是确保了事务的原子性,还是无法解决并发的问题。
综上,还是由前端控制,同一个人不可同时提交多次版本添加的请求,另一方面提高接口处理的速度。后端处理的话,要么加锁,会导致并发性差。而且最终主要还是业务同时提交也不合理,没有内容变化,提交两个版本意义也不大。