高并发下怎么做余额扣减?

2022-11-25 17:10:18 +08:00
 hhhhhh123

这种场景 数据库 是不是只能加锁啊?
假设 数据库中有两个表 一个是流水表 也就是扣款用, 一个是 userinfo 就是余额在这看
那么并发场景下。怎么保证余额是>0 且数据无误。

我的想法是。1 查询余额 如果减去后余额 >=0 则插入扣款后的余额, 这个过程中加锁。 但是这种如果是并发高一点的话是不是很慢啊?

各位有这块的经验吗? 希望可以指点一下。或者也可以讲解一下你们公司的扣款逻辑是啥? 是如何做的 ?

6872 次点击
所在节点    程序员
40 条回复
Jooooooooo
2022-11-25 17:11:52 +08:00
单个 user 哪来的高并发?

你想问的是不是库存?
runningman
2022-11-25 17:15:17 +08:00
行级锁应该就够你用了。
dqzcwxb
2022-11-25 17:21:12 +08:00
分布式锁,队列,单线程
hhhhhh123
2022-11-25 17:25:02 +08:00
@runningman 行锁 怎么避免 死锁
opengps
2022-11-25 17:30:31 +08:00
一楼说出了核心,虽然银行类业务用户多,但是架不住最大的客户操作完一个交易也是需要时间的,这时间不会导致并发
hhhhhh123
2022-11-25 17:31:40 +08:00
@Jooooooooo 那库存这种问题 应该怎么解决?
Jooooooooo
2022-11-25 17:40:58 +08:00
@hhhhhh123

库存的高并发扣减算是比较成熟的东西了, 随便一搜很多的

比如可以搞多层拦截, 如果你只卖 10 个东西, 要是有 1w 人来抢, 那绝大多数流量没有必要到后端, 反正总是能把东西卖出去的. 前端和网关可以直接随机丢弃流量, 流量到了后端后, 可以再加上 MQ 排队和缓存, 最终再到数据库里行锁扣库存.

还有手段比如把库存分散到多行数据上, 随机挑一行扣
ElmerZhang
2022-11-25 17:42:31 +08:00
如果并发不会很高的话不用在数据库上加锁
1. 要扣的钱为 A ,先查 amount 当前值为 B ,代码中判断 B >= A
2. 然后执行 update xxx set amount = amount - A where amount = B
3. 执行看影响行数,如果为 0 ,重新从第 1 步执行
一般只需要重试一次。
dongtingyue
2022-11-25 17:43:43 +08:00
update xxxxx set xxxx where 余额>xx 余额 用 innodb 本身就有行锁,失败返回异常,这点时间肯定要等的
coderxy
2022-11-25 18:15:32 +08:00
乐观锁就够了,修改时判断一下余额与你之前查到的余额是否一致。
git00ll
2022-11-25 18:46:54 +08:00
一锁 二查 三更新
CEBBCAT
2022-11-25 21:09:09 +08:00
@ElmerZhang
@dongtingyue
@coderxy

看三位的回答中好像没有提到事务,不用事务的话遇到意外停机怎么办呢?或者是我理解错了
lovelylain
2022-11-25 21:29:26 +08:00
@CEBBCAT 同时更新多个才要事务,例如给一个人加余额,另一个人减余额。
awanganddong
2022-11-25 22:02:53 +08:00
https://www.51cto.com/article/720873.html

并发扣款,如何保证一致性
沈剑 大佬的文章可以看看
richangfan
2022-11-25 22:23:11 +08:00
update users set balance = balance - 1 where user_id = 123 and balance >= 1;
只在余额大于 1 时扣除用户 123 的 1 块钱
orzwalker111
2022-11-25 22:34:35 +08:00
@richangfan 假设网关、框架重试,会多扣款,解决手段:
1 、悲观锁,使用分布式锁
2 、乐观锁,使用 CAS ,select 得到的 balance 作为 update 的 where 条件,并添加 ver 条件解决 ABA 问题
xuanbg
2022-11-25 22:36:05 +08:00
不要做无意义的事情,15 楼的方法可以很好的解决 OP 你的这个问题。
CEBBCAT
2022-11-25 23:04:47 +08:00
@jobmailcn 是的。我看楼主这个 case 就是需要一边扣钱,一边发放什么东西。
louisliu813
2022-11-25 23:17:01 +08:00
@orzwalker111 是的,我们也是使用 cas ,更新时判断 version ,如果被其他事物更新到 version + 1 了,就 select 新的 balance 和 version 出来,然后基于新 version 做判断,新 balance 做更新。
rqrq
2022-11-26 01:16:38 +08:00
try {
BEGIN;
SELECT balance FROM userinfo WHERE user_id = xxx FOR UPDATE;
逻辑判断,有问题就 throw Exception
UPDATE userinfo...
COMIT;
} catch {
ROLLBACK;
}

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/897920

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX