业务场景是这样的,单一用户的账号,可能会牵扯到并发问题。 就是在同一秒内,该用户的余额会频繁更新。 首先加锁这种情况不太适合。 单纯从 mysql 自身这个问题能否解决? 或者说把金额存入内存。
这是知乎的一个提问
1
fkdog 2021-12-25 11:44:21 +08:00 1
单个用户频繁更新算不上并发。直接加行锁完事了。
就算一秒钟更新 20 下,行锁都是绰绰有余。 你觉得不合适大概率只是你自己觉得,跑一下压测就知道了,大多数情况只是杞人忧天。。 |
2
0ZXYDDu796nVCFxq 2021-12-25 11:48:56 +08:00
每次的变动还得计流水吧
|
3
Saxton 2021-12-25 11:57:30 +08:00
“就是在同一秒内,该用户的余额会频繁更新。 首先加锁这种情况不太适合。”
你这种情况就适合的就是行锁,为什么不太适合 |
4
notejava 2021-12-25 12:12:00 +08:00
乐观锁
|
5
swcat 2021-12-25 12:30:32 +08:00
加个版本,
update xx_table set balance = :balance where id = :id and version_id = :version_id 根据实际情况修改 或者加个 先锁住: select for update 再判断: 这个也得判断下, 判断状态能否更新, 结果得大于 0 吧, 状态可以改吧.... 后更新: 更新数据 id 为为主键 |
6
mitu9527 2021-12-25 13:52:18 +08:00
一般代码都是先读后写,所以除非你代码就是纯更新,否则先去开事务。
搞清楚并发问题所竞争的资源到底是啥。到底是这条记录还是这条记录里面的金额。 如果只是金额,看看能不能用 update ... set money = money + n 解决。 如果上面的方式不行,可以考虑用悲观并发控制策略和乐观并发控制策略,也就是大家常说的悲观锁和乐观锁。选那种,取决于发生的频率和引发的后果到底有多大。 悲观锁最简单的就是用 select for update 或者 redis 来实现,自己根据情况选,两种方式也都不是完美的,引入那种你认为合适的就可以了。 乐观锁一般就是用版本号来实现。 根据情况看看是否有必要做幂等判断,比如状态判断什么的。 |
7
7911364440 2021-12-25 14:36:11 +08:00
把金额也作为更新条件呢?
update t set money = money+n where id = :id and money = :money |
8
awanganddong OP 业务场景就是用户之间赠送礼物,比如同一秒多个用户给一个用户 A 送礼物。
如果这时候加行锁的话,用户这条记录就被锁定了。 问题就在于用户这张数据表属于热表,该条记录的其他字段在其他业务也有更新操作。 担心行锁的话,就会出现死锁的情况。 更可怕的一点,我们这边是主从库配置,现在我暂时把代码切换成查主库,可以降低该概率的发生。 现在对用户操作,都有日志记录。 既然如此,现在我能发现问题,但是对于怎么成本最小的解决这个问题,或者优化这个问题,现在没有太大思路。 |
9
awanganddong OP 类似于客观锁这种机制,所带来问题就是用户操作的失败。
这从代码层面其实没有太大问题,但是对于产品侧,用户体验方面,这个问题就比较严重。 如果乐观锁校验失败,这个就更新失败。 |
10
awanganddong OP 我先压测下关于频繁更新的行锁情况吧。这个很靠谱的。
|
11
sagaxu 2021-12-25 17:06:12 +08:00
行锁每秒 10W 次是扛得住的,高配机器能做到 20W 次
|
12
fiypig 2021-12-25 19:04:46 +08:00 via iPhone
行锁就可以,又不是表锁
|
13
git00ll 2021-12-25 20:21:04 +08:00
for update 没问题的,每秒 4000 次
|