首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于维护产品销售计数器的春季事务隔离级别是什么?

问用于维护产品销售计数器的春季事务隔离级别是什么?
EN

Stack Overflow用户
提问于 2017-04-28 05:35:07
回答 2查看 1.9K关注 0票数 5

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

在我的服务代码中,我有以下事务声明:

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)

在其中,在持久化订单(使用CrudRepository.save())之后,我执行一个select查询来汇总到目前为止所订购的数量,希望select查询将计数所有已提交的订单。但情况似乎并非如此,有时候,柜台的数量比实际的要少。

同样的问题也发生在我的另一个用例:数量限制一个产品。我使用相同的事务隔离设置。在代码中,我将执行一个select查询,看看已经售出了多少,如果我们不能完成订单,就会抛出股票错误。但是对于热门项目,我们有时会超卖该项目,因为每个线程都看不到其他线程中刚刚提交的订单。

那么,对于我的用例而言,READ_COMMITTED是否是正确的隔离级别?还是我应该对这个用例做悲观的锁定?

更新05/13/17

我选择了Ruben的方法,因为我更了解java而不是数据库,所以我走了一条更容易的路。我就是这么做的。

代码语言:javascript
复制
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void updateOrderCounters(Purchase purchase, ACTION action)

我用的是JpaRepository,所以我不直接玩entityManager。相反,我只是将代码放在单独的方法中更新计数器,并按上面的方式进行注释。到目前为止似乎效果很好。我看到超过60个并发连接发出订单,没有超卖,响应时间似乎也不错。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-04-29 09:39:55

根据检索总售出项目的方式,可用选项可能有所不同:

1.如果通过对订单的sum 查询动态计算已售出的商品,则对订单进行动态计数。

我相信在这种情况下,您所拥有的选项是对事务使用SERIALIZABLE隔离级别,因为这是唯一支持range locks并防止phantom reads的方法。但是,我不建议使用这个隔离级别,因为它对您的系统有很大的性能影响(或者只在设计良好的站点上非常小心地使用)。

链接:串行化

2.如果您在产品或与产品关联的其他行上维护一个计数器

在这种情况下,我可能会建议在服务方法中使用row level locking (如select for update )来检查产品的可用性,并增加销售商品的数量。产品布局的高级算法可以类似于以下步骤:

  1. 使用select for update查询(存储库方法上的@Lock(LockModeType.PESSIMISTIC_WRITE))检索存储剩余/售出项目数的行。
  2. 确保检索到的行具有最新的字段值,因为它可以从Hibernate会话级缓存中检索(hibernate只是在id上执行select for update查询来获取锁)。您可以通过调用‘entityManager.refresh(实体)’来实现这一点。
  3. 检查行的count字段,如果值与业务规则一致,则增量/递减。
  4. 保存实体,刷新hibernate会话,并提交事务(显式或隐式)。

以下是元代码:

代码语言:javascript
复制
    @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时在结帐处理例程中)。将锁的影响最小化的另一个有用的技巧是,不是在产品本身上添加“count”列,而是在与产品关联的另一个表上添加“count”列。这将阻止您锁定产品行,因为锁将在不同的行/表组合中获得,这些组合纯粹在签出阶段使用。

链接:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html

摘要

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

很多时候,网上商店选择超卖/预订一些商品,而不是影响表现。

希望这能有所帮助。

票数 3
EN

Stack Overflow用户

发布于 2017-04-28 06:47:10

使用这些事务设置,您应该会看到提交的内容。但是,您的事务处理并不是很严密。可能会发生以下情况:

  • 假设你还有一件存货。
  • 现在两个事务开始了,每个事务都订购了一个项目。
  • 两人都检查库存,看看:“足够好的库存给我。”
  • 两个人都承诺。
  • 现在你卖得太多了。

隔离级可序列化应该能解决这个问题。但

  1. 不同数据库中可用的隔离级别差别很大,因此我认为实际上不能保证将所请求的隔离级别提供给您。
  2. 这严重限制了可伸缩性。这样做的交易应该尽可能短,尽可能少。

根据您正在使用的数据库,使用数据库约束来实现这一点可能是一个更好的主意。例如,在oracle中,您可以创建一个物化视图,计算完整的库存,并对结果设置一个非负约束。

更新

对于物化视图方法,您可以执行以下操作。

  1. 创建物化视图,该视图计算要约束的值,例如订单和。确保在更改底层表内容的事务中更新物化视图。 对于甲骨文,这是通过ON COMMIT子句实现的。 论承诺条款 在提交时指定,表明每当数据库提交在物化视图的主表上操作的事务时,都会发生快速刷新。此子句可能会增加完成提交所需的时间,因为数据库作为提交过程的一部分执行刷新操作。

有关更多详细信息,请参阅6002.htm。

  1. 对该物化视图设置一个check约束,对所需的约束进行编码,例如,该值从不为负值。注意,物化视图只是另一个表,所以您可以像正常情况一样创建约束。 参见前面的示例https://www.techonthenet.com/oracle/check.php
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43672684

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档