一、直接操作数据库,SQL如下:
update t_warehouse_material set count = (count- 10) where warehouse_number ='center_sh' and material_number='Y21002' and batch_number='p_20170822_2' and count>=10 update t_warehouse_material set count = (count- 10) where warehouse_number ='center_sh' and material_number='Y21002' and batch_number='p_20170822_2' and count-10>=0 |
优缺点:不具备通用性,不能记录操作前后日志;
此种方式在高并发环境下会产生数据库性能影响。
第二种方式:直接给数据库字段添加约束,例如像 库存、成绩、年龄 等等这些常规字段正常是不能为负数的,
alter table t_warehouse add constraint CN_Count check (count>=0);
二、select XX for update
select for udpate 则是悲观锁,在查询数据的时候,就将数据锁住。
Product product = select * from table where name = productName for update; if (查询的剩余数量 > buyQuantity) { 影响行数 = update table set surplus = (surplus - buyQuantity) where name = productName ; } else { return "库存不足"; } |
优缺点: 推荐使用,最简单的方案,但是如果事务过大,会有性能问题.操作不当,会有死锁问题。
三、CAS (compare/check and swap/set)
乐观锁: 使用version数据库版本号字段来控制
update 时将version值加1,看select时的version和下一步update时的version是不是同一个版本号,以此来判断当前的数据是不是之前查询到的数量值,还是已被其它线程已经修改过后的数量值。
update t set surplus = 90 ,version = version+1 where id = x andversion = oldVersion ;
优缺点:推荐使用,但是如果数据竞争激烈,则自动重试次数会急剧上升,需要注意。
四、Java 里synchronized同步关键字
public synchronized void buy(String productName, Integer buyQuantity) { // 其他校验... // 校验剩余数量 Product product = 从数据库查询出记录; if (product.getSurplus < buyQuantity) { return "库存不足"; }
// set新的剩余数量 product.setSurplus(product.getSurplus() - quantity); // 更新数据库 update(product); // 记录日志... // 其他业务... } |
优缺点:
1).synchronized 作用范围是单个jvm实例,如果做了集群,分布式等,就没用了
2).synchronized是作用在对象实例上的,如果不是单例,则多个实例间不会同步(这个一般用spring管理bean,默认就是单例)
3).单个jvm时,synchronized也不能保证多个数据库事务的隔离性.这与代码中的事务传播级别,数据库的事务隔离级别,加锁时机等相关.
五、给数据库字段添加范围约束
-- 约束 count 字段必须大于等于0,即库存不能为负数
alter table t_warehouse add constraint CN_Count check (count>=0);