V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
lhx2008
V2EX  ›  问与答

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

  •  
  •   lhx2008 · 2018-12-17 22:06:49 +08:00 via Android · 3043 次点击
    这是一个创建于 2159 天前的主题,其中的信息可能已经有所发展或是发生改变。
    现在有个简单的扣积分的场景,select 出来扣了之后判断积分大于 0,再 update 数据库。所以为了避免一些多线程的问题,想给每个用户 id 加锁执行。但是不知道最佳实践是什么。

    我想到的几个方法
    1. map 设置标志位,不知道有没有一致性问题
    2. map 给每一个 id 配一个 object,用 synchronize 锁这个 object
    3. string.intern,然后直接 sychronize string
    4. 借助 redis 单线程特性做锁,比较麻烦
    5.其他?。。
    第 1 条附言  ·  2018-12-17 23:55:25 +08:00
    感谢大家,比较了下,JPA 的 @Version 最简单,或者自己写一个 CAS 的 update 就 OK 了
    16 条回复    2018-12-18 01:08:23 +08:00
    xipushi
        1
    xipushi  
       2018-12-17 22:32:05 +08:00 via iPhone   ❤️ 1
    如果程序部署多个,程序锁不搞事的。

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

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

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

    这个语句的作用是在数据库操作层面保证:1、这个扣 5 分的操作只会执行一次; 2、并发的扣分操作只会有一个执行成功; 3、任何成功的扣分操作都不会导致用户积分变成负数。
    lhx2008
        8
    lhx2008  
    OP
       2018-12-17 22:51:44 +08:00 via Android
    @Sharuru 服务器端的 version 是用 redis 或者什么记吗,能具体说一下吗
    lhx2008
        9
    lhx2008  
    OP
       2018-12-17 23:00:15 +08:00 via Android
    @yidinghe
    @xipushi
    score=8 就可以了吧,score>5 肯定满足的,不过如果还可以加的话,可能会有 ABA 问题
    x66
        10
    x66  
       2018-12-17 23:31:46 +08:00 via iPhone   ❤️ 1
    @lhx2008 数据库里加一个 version 字段,每次更新+1
    xipushi
        11
    xipushi  
       2018-12-17 23:38:53 +08:00   ❤️ 1
    @lhx2008 我觉得 score=8 就可以了。
    hibernate 的 version 貌似跟 update 语句效果一样,还可以少写几个 SQL。hibernate 默认 session 的生命周期比较反人类。
    xipushi
        12
    xipushi  
       2018-12-17 23:42:38 +08:00   ❤️ 1
    纠正上条,是 JPA 的 session.
    liunull
        13
    liunull  
       2018-12-18 00:01:07 +08:00 via Android
    @lhx2008 看来我表达能不不行😂,其实就是 6 楼说的这个,数据库里,账户表加 version 字段
    lcorange
        14
    lcorange  
       2018-12-18 00:19:11 +08:00
    MySQL 的 select for update 语法能满足要求么
    lhx2008
        15
    lhx2008  
    OP
       2018-12-18 00:35:10 +08:00 via Android
    @lcorange 这个好像很容易死锁
    sagaxu
        16
    sagaxu  
       2018-12-18 01:08:23 +08:00 via Android
    @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
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1180 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:06 · PVG 07:06 · LAX 15:06 · JFK 18:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.