场景
在预约时,每个时间段限制预约人数位30人,新增、取消预约都会修改余量数据,
且后台也可以取消预约,虽然并发量不会有多高,但还是假装有点并发量,就思考了
一些方案。
思考可采取方案
1、synchronized
如果是只有一个接口修改,那么用这个也行,直接来请求就排队,简单暴力;
但是这里有多个接口都会修改,所以也没用
2、Redis
把余量放入Redis中,通过incr/decr来增减;
问题
1)场景问题:该数据几乎每天都会生成,而且我们是代小程序开发,
如果商家入驻多了,数据也就多了,每个时间段的余量都存里面
不太现实
2)因为新增如果出现问题,使用的是Redis,虽然可以通过切面为其
手动回滚,但也是比较麻烦
3、乐观锁
1)Mybatis原生自己写乐观锁
2)Mybatis Plus自带乐观锁
综上考虑,最终采用了Mybatis Plus乐观锁,因为我们用的就是这个,其他的
是在想除了这个有什么其他方式来实现并发,下面是一个测试的Demo。
demo测试
此处略去Spring Boot 整合 Mybatis Plus且配置乐观锁插件 实体类
public class Test implements Serializable {
private static final long serialVersionUID = 1 L;
@TableId ( value = "id" , type = IdType. AUTO)
private Integer id;
private Integer num;
private LocalDateTime updateTime;
@Version
private Integer version;
}
Mybatis Plus乐观锁
1、Mybatis Plus乐观锁支持格式:int,Integer,long,Long,Date,Timestamp,LocalDateTime
2、此处选择的Integer作为类型,因为用时间戳,有可能两个请求在同一秒
修改DB,这样也会造成覆盖,所以最终选择数字类型作为版本号
@Transactional ( isolation = Isolation. SERIALIZABLE)
public void test ( ) {
while ( true ) {
Test test = getById ( 2 ) ;
test. setNum ( test. getNum ( ) + 1 ) ;
boolean result = updateById ( test) ;
if ( result) break ;
}
}
@Transactional作用
1、为方法添加事务
2、可指定隔离级别避免出现脏读、幻读、不可重复读
3、可指定传播机制
4、指定是否可读等
此处核心:一定要指定隔离级别,且为Isolation.READ_COMMITTED,原因如下
1、READ_UNCOMMITTED会出现脏读,所以肯定不能选
2、不填 == REPEATABLE_READ可以避免脏读和不可重复读,但是因为乐观锁,
肯定会修改失败,失败后会继续读取新数据,来再次修改,而如果此时隔离级别为
REPEATABLE_READ,那么会导致读取数据不会变化,因此不能选这个
3、SERIALIZABLE:这个相当于排队操作,不存在脏乱差,但性能低,不考虑
READ_COMMITTED:
该隔离级别只能防止脏读,允许出现幻读和不可重复读,而在乐观锁CAS时,
肯定需要重复读取数据,所以乐观锁的事务隔离级别需要指定为此
Github Demo
小结
其实一开始一直在想,为什么不加@Transactional 就可以读到新数据,加了
就不行,还想过Mybatis缓存的问题,但是禁用后结果还是一样,后面还考虑是不是
MySQL的问题,结果试了几个隔离级别,还真发现了问题,虽然知道脏、幻、不可重
复读和集中隔离级别能干吗,但是不理解,这次过后,才算理解了一些,也算解决了
自己长时间的一个困扰,学东西还是需要思考如何用,之前大多都是看,但不去思考
怎么用,也不去理解,果然,理解万岁- - 。