gpt4 book ai didi

spring - 使用哪个 spring 事务隔离级别来维护已售产品的计数器?

转载 作者:行者123 更新时间:2023-12-01 11:20:51 26 4
gpt4 key购买 nike

我有一个用 Spring Boot + Angular 编写的电子商务网站。我需要在我的产品表中维护一个计数器来跟踪已售出的数量。但是,当许多用户同时订购同一件商品时,计数器有时会变得不准确。

在我的服务代码中,我有以下事务声明:
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
其中,在保留顺序(使用 CrudRepository.save() )之后,我执行了一个选择查询来总结到目前为止订购的数量,希望选择查询能够计算所有已提交的订单。但似乎并非如此,时不时地,计数器小于实际数量。

我的其他用例也会出现同样的问题:数量限制产品。我使用相同的事务隔离设置。在代码中,我将执行选择查询以查看已售出的数量,并在我们无法履行订单时抛出缺货错误。但是对于热门商品,我们有时会超卖该商品,因为每个线程都看不到其他线程中刚刚提交的订单。
READ_COMMITTED也是如此适合我的用例的隔离级别?或者我应该为这个用例做悲观锁定?

更新 05/13/17

我选择了 Ruben 的方法,因为我对 Java 的了解比数据库多,所以我选择了更简单的方法。这就是我所做的。

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void updateOrderCounters(Purchase purchase, ACTION action)

我使用 JpaRepository 所以我不直接玩 entityManager 。相反,我只是将更新计数器的代码放在一个单独的方法中,并如上注释。到目前为止,它似乎运作良好。我已经看到 >60 个并发连接发出订单并且没有超卖,响应时间似乎也不错。

最佳答案

根据您检索已售商品总数的方式,可用选项可能会有所不同:

1. 如果您通过 sum 动态计算已售商品数量订单查询

我相信在这种情况下,您可以选择使用 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) 在存储库方法上)。
  • 确保检索到的行具有最新的字段值,因为它可以从 Hibernate session 级缓存中检索(hibernate 只会在 select for update 上执行 id 查询来获取锁)。您可以通过调用“entityManager.refresh(entity)”来实现这一点。
  • 检查 count行的字段,如果该值符合您的业务规则,则增加/减少它。
  • 保存实体,刷新 hibernate session ,并提交事务(显式或隐式)。

  • 元代码如下:


    @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

    摘要

    请注意,这两种技术都会在您的系统中引入额外的同步点,从而降低吞吐量。因此,请确保通过性能测试或项目中用于测量吞吐量的任何其他技术,仔细测量它对系统的影响。

    网上商店经常选择超卖/预订某些商品而不是影响性能。

    希望这可以帮助。

    关于spring - 使用哪个 spring 事务隔离级别来维护已售产品的计数器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43672684/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com