Qua*_*ing 5 spring hibernate transactions spring-data spring-boot
我有一个用 Spring Boot + Angular 编写的电子商务网站。我需要在我的产品表中维护一个计数器来跟踪已售出的数量。但是,当许多用户同时订购同一商品时,计数器有时会变得不准确。
在我的服务代码中,我有以下事务声明:
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
其中,在保留订单(使用CrudRepository.save())后,我执行一个选择查询来汇总迄今为止订购的数量,希望选择查询能够计算所有已提交的订单。但事实似乎并非如此,计数器有时会小于实际数字。
我的其他用例也出现同样的问题:产品数量限制。我使用相同的事务隔离设置。在代码中,我将执行一个选择查询来查看已售出多少数量,如果我们无法履行订单,则会抛出缺货错误。但对于热门商品,我们有时会超卖该商品,因为每个线程看不到刚刚在其他线程中提交的订单。
那么READ_COMMITTED隔离级别适合我的用例吗?或者我应该对此用例进行悲观锁定?
更新 2017 年 5 月 13 日
我选择了 Ruben 的方法,因为我对 java 的了解比对数据库的了解更多,所以我选择了更简单的方法。这就是我所做的。
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void updateOrderCounters(Purchase purchase, ACTION action)
我使用JpaRepository,所以我不直接玩entityManager。相反,我只是将更新计数器的代码放在一个单独的方法中,并如上注释。到目前为止似乎运作良好。我见过超过 60 个并发连接下订单,没有超卖,响应时间似乎也不错。
根据您检索已售商品总数的方式,可用选项可能会有所不同:
sum1. 如果您通过订单查询动态计算已售商品数量
我相信在这种情况下,您可以选择使用SERIALIZABLE事务的隔离级别,因为这是唯一支持range locks和防止phantom reads. 但是,我真的不建议使用此隔离级别,因为它会对您的系统产生重大性能影响(或者仅在设计良好的位置上非常小心地使用)。
链接: https: //dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html#isolevel_serializable
2. 如果您维护产品计数器或与产品关联的其他行
在这种情况下,我可能会建议在服务方法中使用row level locking例如select for update检查产品的可用性并增加已售商品数量。产品植入的高级算法可能类似于以下步骤:
select for update使用查询(@Lock(LockModeType.PESSIMISTIC_WRITE)在存储库方法上)检索存储剩余/已售商品数量的行。select for update在该行上执行查询id来获取锁)。您可以通过调用“entityManager.refresh(entity)”来实现此目的。count该行的字段,如果该值符合您的业务规则,则增加/减少它。元代码如下:
    @Transactional
    public Product performPlacement(@Nonnull final Long id) {
        Assert.notNull(id, "Product id should not be null");
        entityManager.flush();
        final Product product = entityManager.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE);
        // Make sure to get latest version from database after acquiring lock, 
        // since if a load was performed in the same hibernate session then hibernate will only acquire the lock but use fields from the cache
        entityManager.refresh(product);
        // Execute check and booking operations
        // This method call could just check if availableCount > 0
        if(product.isAvailableForPurchase()) {
            // This methods could potentially just decrement the available count, eg, --availableCount
            product.registerPurchase();
        }
        // Persist the updated product 
        entityManager.persist(product);
        entityManager.flush();
        return product;
    }
这种方法将确保任何两个线程/事务都不会同时对存储产品计数的同一行执行检查和更新。
然而,正因为如此,它也会对您的系统产生一些性能下降影响,因此必须确保原子增量/减量在购买流程中尽可能多地使用并且尽可能少使用(例如,就在购买流程中)。顾客点击时的结帐处理例程pay)。最小化锁定影响的另一个有用技巧是不在产品本身上添加“计数”列,而是在与产品关联的另一个表上添加“计数”列。这将阻止您锁定产品行,因为将在纯粹在结账阶段使用的不同行/表组合上获取锁。
链接:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
概括
请注意,这两种技术都会在系统中引入额外的同步点,从而降低吞吐量。因此,请确保通过性能测试或项目中用于测量吞吐量的任何其他技术仔细测量它对您的系统的影响。
网上商店经常选择超额销售/预订某些商品,而不是影响业绩。
希望这可以帮助。
| 归档时间: | 
 | 
| 查看次数: | 2293 次 | 
| 最近记录: |