,此时,商品数据表现为一个共享数据,所以存在微弱的并发,通常表现为数据的脏读,例如操作员A,B同时对一个商品信息维护,我们希望只能有一个操作员修改成功,另外一个操作员得到错误提示(该商品信息已经发生变化),否则,两个人都以为自己修改成功了,但是其实只有一个人完成了操作,另一个人的操作被覆盖了。
这个场景表现为:存在并发,需要控制,允许失败,场景乐观。
通常我建议这种场景使用乐观锁,即在商品属性添加一个version字段标记修改的版本,这样两个操作员拿到同一个版本号,第一个操作员修改成功后版本号变化,另一个操作员的修改就会失败了。
class Goods{
@Version
int version;
}
//Transaction start
try{
Goods goods = goodsDao.findById("1");
goods.setName("newName");
goods.setPrice(goods.getPrice()+100.00);
...//其他耗时操作
goodsDao.save(goods);
}catch(org.hibernate.StaleObjectStateException e){
//返回给前台
}
//Transaction commit
springdata配合jpa可以自动捕获version异常,也可以自动手动对比。
第三个场景
这个场景表现为:存在频繁的并发,需要控制,不允许失败,场景悲观。
强调一下,本例不应该使用在项目中,只是为了举例而设置的一个场景,因为这种贫血模型无法满足复杂的业务场景,而且依靠单机事务来保证一致性,并发性能和可扩展性能不好。
一个简易的秒杀场景,大量请求在短时间涌入,是不可能像第二种场景一样,100个并发请求,一个成功,其他99个全部异常的。
设计方案应该达到的效果是:有足够库存时,允许并发,库存到0时,之后的请求全部失败;有足够金额时,允许并发,金额不够支付时立刻告知余额不足。
可以利用数据库的行级锁,
update set balance = balance – money where userId = ? and balance >= money;
update stock = stock – number where goodsId = ? and stock >= number ; 然后在后台 查看返回值是否影响行数为1,判断请求是否成功,利用数据库保证并发。
需要补充一点,我这里所讲的秒杀,并不是指双11那种级别的秒杀,那需要多层架构去控制并发,前端拦截,负载均衡….不能仅仅依赖于数据库的,会导致严重的性能问题。为了留一下一个直观的感受,这里对比一下oracle,mysql的两个主流存储引擎:innodb,myisam的性能问题。
oracle:
10000个线程共计1000000次并发请求:共花费:101017 ms =>101s
innodb:
10000个线程共计1000000次并发请求:共花费:550330 ms =>550s
myisam:
10000个线程共计1000000次并发请求:共花费:75802 ms =>75s
可见,如果真正有大量请求到达数据库,光是依靠数据库解决并发是不现实的,所以仅仅只用数据库来做保障而不是完全依赖。需要根据业务场景选择合适的控制并发手段。