Java web 给一个 id 加锁的最佳实践是什么

2018-12-17 22:06:49 +08:00
 lhx2008
现在有个简单的扣积分的场景,select 出来扣了之后判断积分大于 0,再 update 数据库。所以为了避免一些多线程的问题,想给每个用户 id 加锁执行。但是不知道最佳实践是什么。

我想到的几个方法
1. map 设置标志位,不知道有没有一致性问题
2. map 给每一个 id 配一个 object,用 synchronize 锁这个 object
3. string.intern,然后直接 sychronize string
4. 借助 redis 单线程特性做锁,比较麻烦
5.其他?。。
3077 次点击
所在节点    问与答
16 条回复
xipushi
2018-12-17 22:32:05 +08:00
如果程序部署多个,程序锁不搞事的。

一个写库的话,直接 update。update set count=count-1 where count>1,影响行数大于 1,说明成功。

Zookeeper,redis
lhx2008
2018-12-17 22:37:49 +08:00
@xipushi 集群的话,还可以加一致 hash 或者 redis
liunull
2018-12-17 22:39:44 +08:00
我们公司是给账户记录加版本号,类似 CAS,冲突就重试,一般单个用户并发修改数据库的概率很小
lhx2008
2018-12-17 22:43:05 +08:00
@liunull 是请求的时候附带上一个的版本号吗
lhx2008
2018-12-17 22:44:00 +08:00
@liunull 也不一定是用户 id,商品 id 减库存啥的也可以用这一套
Sharuru
2018-12-17 22:45:26 +08:00
乐观锁呀,带 version 字段就完事儿了。
yidinghe
2018-12-17 22:50:31 +08:00
假设用户积分为 8,现在要扣掉 5 分,那么 SQL 语句为:

update user set score=score-5 where userid=xxx and score=8 and score>=5

这个语句的作用是在数据库操作层面保证:1、这个扣 5 分的操作只会执行一次; 2、并发的扣分操作只会有一个执行成功; 3、任何成功的扣分操作都不会导致用户积分变成负数。
lhx2008
2018-12-17 22:51:44 +08:00
@Sharuru 服务器端的 version 是用 redis 或者什么记吗,能具体说一下吗
lhx2008
2018-12-17 23:00:15 +08:00
@yidinghe
@xipushi
score=8 就可以了吧,score>5 肯定满足的,不过如果还可以加的话,可能会有 ABA 问题
x66
2018-12-17 23:31:46 +08:00
@lhx2008 数据库里加一个 version 字段,每次更新+1
xipushi
2018-12-17 23:38:53 +08:00
@lhx2008 我觉得 score=8 就可以了。
hibernate 的 version 貌似跟 update 语句效果一样,还可以少写几个 SQL。hibernate 默认 session 的生命周期比较反人类。
xipushi
2018-12-17 23:42:38 +08:00
纠正上条,是 JPA 的 session.
liunull
2018-12-18 00:01:07 +08:00
@lhx2008 看来我表达能不不行😂,其实就是 6 楼说的这个,数据库里,账户表加 version 字段
lcorange
2018-12-18 00:19:11 +08:00
MySQL 的 select for update 语法能满足要求么
lhx2008
2018-12-18 00:35:10 +08:00
@lcorange 这个好像很容易死锁
sagaxu
2018-12-18 01:08:23 +08:00
@lhx2008 只要用 innodb,你直接写 update where version=xxx 一样可能产生行锁,MySQL 会检测死锁。select for update 反而是避免死锁的办法。

To avoid deadlocks when performing multiple concurrent write operations on a single InnoDB table, acquire necessary locks at the start of the transaction by issuing a SELECT ... FOR UPDATE statement for each group of rows expected to be modified, even if the data change statements come later in the transaction.

https://dev.mysql.com/doc/refman/8.0/en/internal-locking.html

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

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

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

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

© 2021 V2EX