库存扣减,余额扣减,应该使用乐观锁的方式吗?

2020-04-29 09:56:54 +08:00
 watzds
和这种直接扣减的方式相比,有什么好处呢?
UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff;

有些文章说是乐观锁方式可以在重试时保证幂等性,不过什么时候会重试呢
1. 超时?如果重试又失败,那怎么判断是之前已经成功还是后来竞争失败呢?怎么返回结果给上层呢
2. 如果是获取版本号一起被重试,那也没什么幂等性了

总觉得没说服力,五十步笑百步的感觉,想要实现幂等性,最终还是需要其他辅助手段。
这种方式是有什么其他原因吗,比如性能,正确性?

说是幂等性优点的(架构师之路): https://mp.weixin.qq.com/s/xXju0y64KKUiD06QE0LoeA

还有《企业 IT 架构转型之道》这本书里,是将乐观锁扣减方式和 select for update 相比较,没提什么 where 限制扣减,幂等性之类


大家是用什么方式,这种乐观锁覆盖实现扣减到底有什么好处呢?
6593 次点击
所在节点    程序员
27 条回复
xuanbg
2020-04-29 10:16:22 +08:00
单机系统能不用锁就不要用锁。分布式系统用的也不是单机的锁,要用分布式锁才有用。
watzds
2020-04-29 10:23:36 +08:00
@xuanbg #1 所谓乐观锁方式和下面这种不都会有行锁吗,索引正确情况
UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff;
watzds
2020-04-29 10:25:04 +08:00
所谓乐观锁是这种方式:UPDATE t_yue SET money=38, version=$version_new WHERE uid=$uid AND version=$version_old
kingwrcy
2020-04-29 10:28:33 +08:00
库存的扣减并发概率比较高,建议乐观锁
余额的扣减病发率比较低,比较建议悲观锁,涉及到金钱的,还是需要严谨一点.
xuanbg
2020-04-29 11:05:26 +08:00
@watzds UPDATE t_yue SET money=38, version=$version_new WHERE uid=$uid AND version=$version_old 这种方案的问题是并发的情况下只有一个线程能成功,其他线程都会失败。

数据库的行锁哪能没有呢,正是因为有行锁,对同一条记录进行更新时才会排队。导致后面相同的 sql 会因为 where 中的 version 值变了导致条件不符而失败。而 UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff;这种方案就不会受影响。
optional
2020-04-29 11:10:24 +08:00
#3 才是乐观锁,你这个不是。
optional
2020-04-29 11:12:37 +08:00
@xuanbg version 变了报错才是正常的逻辑,方便事务回滚。
xmh51
2020-04-29 11:16:20 +08:00
第一个是不太友好的点是你拿不到当时的余额,第二个是你需要对两个字段做关联修改的时候就有问题了
watzds
2020-04-29 11:31:45 +08:00
@xmh51 #8 假如需要拿到余额,做一下相关操作之后,再扣款,那乐观锁其实是替换了 for update 倒是有意义的
比如要求余额是素数才能扣款,那只能 for update 或者乐观锁了

不过一般余额足够就行,我没想到那样的业务场景
watzds
2020-04-29 11:35:13 +08:00
@xuanbg #5 哦,那你是觉得一般应该用 UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff; 而不是乐观锁是吗?其实我是这么觉得,只是看网上书上都说乐观锁方式比较多
lhx2008
2020-04-29 11:38:13 +08:00
MYSQL 配合事务可以基本保证幂等性的,超时没事,事务提交不了。语句执行成功就是成功,而且只能执行成功一次。
lhx2008
2020-04-29 11:40:36 +08:00
而且主要问题是多个事务并发的问题,而不是你自己重试的问题。比如说有个用户点了一下+10,马上又点一下+50,同时到数据库,那就可能有一个会执行失败,要不然就有可能最后只加了 50
watzds
2020-04-29 11:43:20 +08:00
@lhx2008 #11 这个我有一点疑问,如果是 commit 发往 数据库,数据库收到了也提交了事务,但是应用没收到网络响应,连接断了,不知应用是怎么处理的,是会超时异常,还是重新建立连接后再次向数据库查询事务是否提交?
sioncheng
2020-04-29 11:43:55 +08:00
乐观锁和幂等性没什么相关性
watzds
2020-04-29 11:46:08 +08:00
@lhx2008 #12 这个 sql 并没有并发问题,是能保证正确的,因为 UPDATE 是当前读,会加行数

UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff;
iffi
2020-04-29 11:51:29 +08:00
用乐观锁是为了提高并发性能,如果高并发场景下,你用悲观锁,系统吞吐量就会下降;当然在高并发场景下使用乐观锁,会有很多失败的请求,看你需求场景是否需要支持重试机制。
lhx2008
2020-04-29 11:54:22 +08:00
@watzds #15 我说的就是乐观锁的作用
@watzds #13 这个问题通常不是很容易发生,可能需要依赖客户端重连之后再去检查。不过只是这一条语句不知道有没有执行成功,不影响后面的执行,因为版本号已经变更了。
watzds
2020-04-29 11:57:09 +08:00
@iffi #16 如果是 for update 这种悲观锁,性能影响应该是挺大的,不过这种扣减方式性能和乐观锁会有差别吗?

UPDATE stock SET amount = amount - $diff WHERE id=$id AND amount>$diff;
Aresxue
2020-04-29 12:00:05 +08:00
乐观锁的本质上是消除锁定, 适用于高并发下 读多(读是无锁)写少 的情况, 用乐观锁就是写也不加锁,然后通过结果去重试, 如果写的请求很多极端点全是写的请求, 那么还不如悲观锁的效率高
noobsheldon
2020-04-29 13:34:17 +08:00
redis

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

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

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

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

© 2021 V2EX