简体   繁体   English

检查和更新表中的记录时潜在的并发问题

[英]Potential concurrency issue while checking and updating a record in table

Here is the case, 就是这种情况

A member has to redeem a token to access (unlock) a given item. 成员必须兑换令牌才能访问(解锁)给定项目。 The relevant database tables are: 相关的数据库表是:

Table 1 表格1

Table MEMBER_BALANCE: MEMBER_ID, TOKEN_BALANCE

Table 2 表2

Table UNLOCKED_ITEM: MEMBER_ID, DATE_UNLOCKED, ITEM_ID

Checks, or constraints, that I need to enforce are 我需要执行的检查或约束是

  1. TOKEN_BALANCE must be > 0 when the user tries to unlock an item, and 当用户尝试解锁商品时,TOKEN_BALANCE必须> 0,并且
  2. The user hasn't unlocked the same item before. 用户之前未解锁相同的物品。

My gut inclination is to write a simple method in MemberService.java: 我的直觉是在MemberService.java中编写一个简单的方法:

@Transactional
public void unlockItem(Member member, Item item){
    memberBalanceDAO.decrementBalance(member);
    itemDAO.unlockItem(member, item);
}

I've dealt with the second requirement by adding a unique constraint on MEMBER_ID / ITEM_ID pair on the UNLOCKED_ITEM table. 我已经通过在UNLOCKED_ITEM表上的MEMBER_ID / ITEM_ID对上添加unique约束来处理第二个要求。

I think, the only thing I need to take care of would be, users trying to unlock many items at the same time, with TOKEN_BALANCE requirement not met. 我认为,我唯一需要照顾的是,用户试图同时解锁许多物品,而未满足TOKEN_BALANCE要求。 For example, TOKEN_BALANCE is 1, but the user clicks to unlock two items, virtually, at the same time. 例如, TOKEN_BALANCE为1,但是用户单击以同时虚拟地解锁两个项目。

Below is my MemberBalanceDAO.decrementBalance method: 下面是我的MemberBalanceDAO.decrementBalance方法:

@Transactional
public void decrementBalance(Member member) {
    MemberBalance memberBalance = this.findMemberBalance(member);
    if (memberBalance.getTokens() >= 1) {
        memberBalance.setTokens(memberBalance.getTokens() - 1);
        this.save(memberBalance);
    } else {
        throw new SomeCustomRTException("No balance");
    }
}

I don't think this protects me from the TOKEN_BALANCE = 1 usecase. 我认为这不会保护我免受TOKEN_BALANCE = 1个用例的影响。 I'm worried about with multiple unlock requests at the same time. 我担心同时有多个解锁请求。 If the balance is 1, I could get two calls to decrementBalance() at the same time both committing the balance to 0, but then also two successful calls to itemDAO.unlockItem(...) as well, right? 如果余额为1,那么我可以同时调用两次decrementBalance()并将余额都提交为0,但是也可以成功调用两次itemDAO.unlockItem(...) ,对吗?

How should I implement this? 我应该如何实施呢? Should I set the service level method's transaction to isolation = Isolation.SERIALIZABLE ? 我应该将服务级别方法的事务设置isolation = Isolation.SERIALIZABLE吗? Or is there a cleaner/better way to approach this? 还是有一种更清洁/更好的方式来解决这个问题?

I would rather suggest you to introduce version column in member_balance table. 我宁愿建议您在member_balance表中引入version列。 Refer to the docs, Optimistic Locking . 请参阅文档乐观锁定

As you mentioned that you couldn't modify the schema; 如前所述,您无法修改架构; you can go with versionless optimistic locks, explained here . 您可以使用无版本的乐观锁,如此处所述

Or you might like to go for pessimistic locking, explained here . 或者,您可能想要进行悲观锁定,如此处所述 Then, you can modify your method, decrementBalance() , to fetch the member balance there, don't use findMemberBalance() . 然后,您可以修改方法decrementBalance() ,以在那里获取成员余额,请不要使用findMemberBalance() For example, 例如,

@Transactional
public void decrementBalance(Member member) {
    MemberBalance memberBalance = entityManager.find(
        MemberBalance.class, member.id, LockModeType.PESSIMISTIC_WRITE,             
        Collections.singletonMap( "javax.persistence.lock.timeout", 200 ) //If not supported, the Hibernate dialect ignores this query hint.
    );
    if (memberBalance.getTokens() >= 1) {
        memberBalance.setTokens(memberBalance.getTokens() - 1);
        this.save(memberBalance);
    } else {
        throw new SomeCustomRTException("No balance");
    }
}

NB: It might not work as it is; 注意:它可能无法按原样工作; it's just to provide you some hints. 只是为您提供一些提示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM