如何防止并发操作时账户余额的扣费和充值冲突?

账户余额,充值和扣费,
怎么防止在扣费的时候拿的余额是10块钱,要扣1块钱,然后余额是9块钱
然后同一时间充值拿的余额也是10块钱,充值10块钱。应该是最后充完余额为19元,怎么防止这个并发呢,请问sql应该怎么去写

怎么防止这个并发呢

阅读 2.2k
5 个回答
✓ 已被采纳

如果站在必须保证扣费和充值都成功的维度

  • 用悲观锁的方式实现
    使用用户id做为锁标识,例如 change:balance:userId:123
    当变更账户余额时(充值或扣费)尝试获得锁,如果获得锁成功则继续变更账户余额,否则阻塞,直到尝试获得锁实施变更账户余额操作,从而保证整个变更余额过程的原子性;
  • 队列的方式实现
    通过队列来实现串行和解耦,将扣费和充值动作都丢到FIFO队列中,在由一个消费者依次去队列中取出要执行的action,这样保证整个更余额过程是同步的(synchronized);

    当然这两种方案也可以结合使用

如果站在高性能的维度

  • 可通过乐观锁方式实现
    即在获取账户余额时同时获得此时数据的verson,
    当变更余额时加上version的判断,例如

    udpate account_balance 
    set balance = balance + :changeNum, version = version + 1
    where user_id = :userId and version = :version
    :changeNum为传入的变更余额(扣1块或充值10块),
    :userId为传入的用户id,
    :version为传入的数据verison

此时,sql的响应行数为1则表示更新成,0则表示更新失败(数据已经被其他线程更新)。

扣费操作:

START TRANSACTION;

-- 假设 user_id = 123
SELECT balance FROM accounts WHERE user_id = 123 FOR UPDATE;

-- 假设需要扣除金额为 1
UPDATE accounts SET balance = balance - 1 WHERE user_id = 123;

COMMIT;

充值操作:

START TRANSACTION;

-- 假设 user_id = 123
SELECT balance FROM accounts WHERE user_id = 123 FOR UPDATE;

-- 假设充值金额为 10
UPDATE accounts SET balance = balance + 10 WHERE user_id = 123;

COMMIT;

业务代码层面上做,通过加锁,同一时刻只允许扣费或只允许充值。

最简单方案就就是通过sql来实现,比如用以下类似的sql来实现,基本能控制并发

update set money = money - 10 where id=1 and  money-10>0

扣款逻辑改成同步单线程,中间用一个缓冲区缓存扣款任务。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题