不懂就问系列, web + 并发 + 锁 问题,具体情况在正文 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
chaleaochexist
V2EX    程序员

不懂就问系列, web + 并发 + 锁 问题,具体情况在正文

  •  
  •   chaleaochexist 2019-06-03 10:08:11 +08:00 3639 次点击
    这是一个创建于 2323 天前的主题,其中的信息可能已经有所发展或是发生改变。
    不知道这样的问题,如何归纳成主题.

    数据库写操作.
    其中一个字段`version`需要+1 操作.
    在高并发情况下,如何确保 version 唯一.

    table 大概是这样的

    | version | object_name | operation |
    | :-----: | :---------: | :-------: |
    | 1 | 小明 | 吃饭 |
    | 2 | 小明 | 吃饭 |
    | 1 | 小红 | 吃饭 |
    | 1 | 小红 | 睡觉 |

    version 只在 object_name 和 operation 相同的情况下+1.
    1. 自增不行.
    2. 可以加一个唯一约束.
    3. 有没有在后端的代码里面加锁控制? 求最佳实践和通用解决方案.
    30 条回复    2019-06-06 16:14:27 +08:00
    chaleaochexist
        1
    chaleaochexist  
    OP
       2019-06-03 10:09:05 +08:00
    v2markdown 不支持表格吗...
    Maboroshii
        2
    Maboroshii  
       2019-06-03 10:12:42 +08:00
    redis ?
    zhengxiaowai
        3
    zhengxiaowai  
       2019-06-03 10:15:39 +08:00
    行锁了解一下
    chaleaochexist
        4
    chaleaochexist  
    OP
       2019-06-03 10:19:51 +08:00
    @zhengxiaowai +1 操作是在代码里执行. 行锁如何控制? 没想明白.
    liuzhedash
        5
    liuzhedash  
       2019-06-03 10:21:33 +08:00
    2、唯一约束不能直接解决问题:当发现重复记录的时候,如何处理?
    3、理论上可以,但是假设未来需要多台服务器进行负载均衡,这个方法就失效了。
    3L 的行锁应该可以,但是我没有试过。
    之前我遇到类似的问题是通过把请求先缓存到 redis 队列中,然后逐个插入数据库解决的。
    NaVient
        6
    NaVient  
       2019-06-03 10:22:18 +08:00
    redis 实现分布式锁不行吗 以{table_name}_{id}的方式做键值
    NewDraw
        7
    NewDraw  
       2019-06-03 10:24:23 +08:00 via Android
    没看明白你说的啥意思。
    是说,在现有表的基础上再插入一条 小明,吃饭,该记录 version 的值要是 3 嘛?

    如果是上面的问题,那么就是问题就可以转化为如何解决幻读,四种事务隔离级别可以了解一下,这种情况只能用表锁了。
    chaleaochexist
        8
    chaleaochexist  
    OP
       2019-06-03 10:25:20 +08:00
    @liuzhedash
    谢谢.
    2. 唯一约束可以解决问题, 代码里面捕获异常,然后+2,+3...
    3. 您说的对.谢谢.
    4. 行锁您能从理论上解释一下吗?我没反应过来,虽然我知道行锁是啥.也许对行锁了解不扎实.给点提示?
    5. redis 可行. 我在想想.
    airfling
        9
    airfling  
       2019-06-03 10:25:28 +08:00
    高并发应该是读的高并发吧,那就是加个写的锁,当写入完毕释放锁
    guiling
        10
    guiling  
       2019-06-03 10:26:30 +08:00
    这不是乐观锁么?
    update b set version=version+1 where version=?
    更新之前先查一下版本,需要其他限制加在 where 后面
    chaleaochexist
        11
    chaleaochexist  
    OP
       2019-06-03 10:26:33 +08:00
    @NewDraw
    对两个线程同时对数据库 小明 吃饭 +1 写了两条 version=3 的数据,这是不可以被接受的.
    NewDraw
        12
    NewDraw  
       2019-06-03 10:28:50 +08:00 via Android   1
    额,是我说的意思啊,两个线程并发写入,就会有幻读的情况,可以让数据库隔离级别设为最高级。
    chaleaochexist
        13
    chaleaochexist  
    OP
       2019-06-03 10:29:21 +08:00
    @airfling 考虑这种情况
    两个线程 已经读到 version = 2
    各自+1...

    写锁的约束会约束到代码吗?
    这块不懂.
    请多指教.
    chaleaochexist
        14
    chaleaochexist  
    OP
       2019-06-03 10:30:22 +08:00
    @NewDraw 我刚才脑袋抽了.我在想想,好像你说的对.
    mooncakejs
        15
    mooncakejs  
       2019-06-03 10:30:36 +08:00   1
    太追求数据库层面解决不是一个好的实践,锁缓存是个好主意,#6 楼说的分布式锁。
    liukanshan
        16
    liukanshan  
       2019-06-03 10:35:12 +08:00
    每一次更新操作之前 检查下版本号

    def fun(){
    def version = select version from xxx;
    //do something


    update xxx set field=value,version = version + 1 where version = $version;
    }
    night98
        17
    night98  
       2019-06-03 10:38:57 +08:00
    新增一个唯一主键,按唯一主键 + version 确定单条记录。
    然后执行楼上说的:
    update b set version=version+1 where version=?
    再根据返回的修改条数的数据判断是否修改成功。
    或者就是你这种,联合主键 + version 确定单条记录,更新执行完毕后根据返回的更新条数判断是否更新成功。
    fantastM
        18
    fantastM  
       2019-06-03 10:52:22 +08:00
    不知道你的程序逻辑里,具体需要执行哪些 SQL......粗略看下来,似乎是「幻读」的问题。你可以对着 MySQL 的文档看看,如果是「幻读」的话,那可以用 MySQL 的隔离级别来解决。当然也可以用分布式锁解决

    参考资料:
    https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
    https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
    https://www.cnblogs.com/zhoujinyi/p/3437475.html
    sleepiforest
        19
    sleepiforest  
       2019-06-03 10:54:00 +08:00
    需求是插入的时候,object_name 和 operation 相同的情况下,在原有最大的基础上+1,再插一条么……为啥要干这种事情。
    mysql 的话直接用单机事务吧……
    csys
        20
    csys  
       2019-06-03 11:19:02 +08:00 via Android   2
    低并发非分布式: 内存锁
    低并发分布式: 数据库行锁 /乐观锁 /分布式锁
    高并发非分布式: eventsourcing(queue+batch commit)
    高并发分布式: actor+eventsourcing
    NullErro
        21
    NullErro  
       2019-06-03 11:19:05 +08:00
    这种情况一般不都是在代码端处理的吗?最直接暴力的就是在后端更新 version 值的时候加锁
    gz911122
        22
    gz911122  
       2019-06-03 11:21:25 +08:00   1
    行锁就可以

    select for update 即可
    javlib
        23
    javlib  
       2019-06-03 12:05:05 +08:00
    我也有同样的问题,我的要求低一些,不要求严格递增,只要能自增就行。

    如果用 redis 做分布式锁,感觉太麻烦,而且降低了写入性能,不知道大佬门有没有简单的只用 sql 的方法。

    我目前的做法是用乐观锁,给每个集合加了一个单独的行,比如(小明,吃饭,version ),这一行的 version 专门用来做小明吃饭的自增,每次自增用乐观锁,每次插入新的数据,先更新这一行的 version,如果更新成功,就用新的 version 作为插入的 version,如果失败,则退回重试。
    FarAhead
        24
    FarAhead  
       2019-06-03 12:21:05 +08:00
    在这块业务的代码加个锁,保证多个进程不会同时执行这块代码,锁可以用 redis 实现
    chmaple
        25
    chmaple  
       2019-06-03 12:53:58 +08:00
    @chaleaochexist
    两个都读到 2,然后各自+1,执行 update set ...version = where id= and version=到数据库的时候,只有一条能执行成功,另一条 update 的返回 int 是 0,没有匹配的记录。
    数据库 MySQL 默认级别是 repeatableRead。
    就是要做好事务回滚的准备。
    chmaple
        26
    chmaple  
       2019-06-03 12:57:31 +08:00   1
    @javlib 用 redis 还快一点,乐观锁重试失败不如竞争 redis 锁的性能吧
    其实也要看业务的复杂程度,如果本身失败的可能性不高,冲突的几率很小的话,乐观锁也够了,实现起来还简单
    但是如果并发的概率比较大,业务的要求还比较高的话,redis 分布式锁更好些。
    leon0903
        27
    leon0903  
       2019-06-03 14:37:07 +08:00
    我有一个功能和你这个差不多, 我就是用的唯一索引, 每次更新前 先查出当前的最大 version,然后加 1,插入到数据库。 我们对并发的要求比较低,插入失败的时候直接返回错误了,可以重新执行一次就可以了。
    conn4575
        28
    conn4575  
       2019-06-03 21:55:11 +08:00 via Android
    你这种情况要在 mysql 上处理要使用最高级别的串行化事务级别,很容易出现死锁,最好直接用 redis 锁来做
    pisc
        29
    pisc  
       2019-06-03 22:49:36 +08:00
    v2 上对数据库的理解都这么次么。。。一个 select for update 就能解决的事情,扯这么多有的没的真是误导人
    wejaylyn
        30
    wejaylyn  
       2019-06-06 16:14:27 +08:00
    unique index + select for update
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     916 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 18:50 PVG 02:50 LAX 11:50 JFK 14:50
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86