• 请不要在回答技术问题时复制粘贴 AI 生成的内容
chaleaochexist
V2EX  ›  程序员

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

  •  
  •   chaleaochexist · Jun 3, 2019 · 4472 views
    This topic created in 2719 days ago, the information mentioned may be changed or developed.
    不知道这样的问题,如何归纳成主题.

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

    table 大概是这样的

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

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

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

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

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


    update xxx set field=value,version = version + 1 where version = $version;
    }
    night98
    night98
    Jun 3, 2019
    17
    新增一个唯一主键,按唯一主键 + version 确定单条记录。
    然后执行楼上说的:
    update b set version=version+1 where version=?
    再根据返回的修改条数的数据判断是否修改成功。
    或者就是你这种,联合主键 + version 确定单条记录,更新执行完毕后根据返回的更新条数判断是否更新成功。
    fantastM
    fantastM
    Jun 3, 2019
    18
    不知道你的程序逻辑里,具体需要执行哪些 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
    sleepiforest
    Jun 3, 2019
    19
    需求是插入的时候,object_name 和 operation 相同的情况下,在原有最大的基础上+1,再插一条么……为啥要干这种事情。
    mysql 的话直接用单机事务吧……
    csys
    csys
    Jun 3, 2019 via Android❤️2
    20
    低并发非分布式: 内存锁
    低并发分布式: 数据库行锁 /乐观锁 /分布式锁
    高并发非分布式: eventsourcing(queue+batch commit)
    高并发分布式: actor+eventsourcing
    NullErro
    NullErro
    Jun 3, 2019
    21
    这种情况一般不都是在代码端处理的吗?最直接暴力的就是在后端更新 version 值的时候加锁
    gz911122
    gz911122
    Jun 3, 2019❤️1
    22
    行锁就可以

    select for update 即可
    javlib
    javlib
    Jun 3, 2019
    23
    我也有同样的问题,我的要求低一些,不要求严格递增,只要能自增就行。

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

    我目前的做法是用乐观锁,给每个集合加了一个单独的行,比如(小明,吃饭,version ),这一行的 version 专门用来做小明吃饭的自增,每次自增用乐观锁,每次插入新的数据,先更新这一行的 version,如果更新成功,就用新的 version 作为插入的 version,如果失败,则退回重试。
    FarAhead
    FarAhead
    Jun 3, 2019
    24
    在这块业务的代码加个锁,保证多个进程不会同时执行这块代码,锁可以用 redis 实现
    chmaple
    chmaple
    Jun 3, 2019
    25
    @chaleaochexist
    两个都读到 2,然后各自+1,执行 update set ...version = where id= and version=到数据库的时候,只有一条能执行成功,另一条 update 的返回 int 是 0,没有匹配的记录。
    数据库 MySQL 默认级别是 repeatableRead。
    就是要做好事务回滚的准备。
    chmaple
    chmaple
    Jun 3, 2019❤️1
    26
    @javlib 用 redis 还快一点,乐观锁重试失败不如竞争 redis 锁的性能吧
    其实也要看业务的复杂程度,如果本身失败的可能性不高,冲突的几率很小的话,乐观锁也够了,实现起来还简单
    但是如果并发的概率比较大,业务的要求还比较高的话,redis 分布式锁更好些。
    leon0903
    leon0903
    Jun 3, 2019
    27
    我有一个功能和你这个差不多, 我就是用的唯一索引, 每次更新前 先查出当前的最大 version,然后加 1,插入到数据库。 我们对并发的要求比较低,插入失败的时候直接返回错误了,可以重新执行一次就可以了。
    conn4575
    conn4575
    Jun 3, 2019 via Android
    28
    你这种情况要在 mysql 上处理要使用最高级别的串行化事务级别,很容易出现死锁,最好直接用 redis 锁来做
    pisc
    pisc
    Jun 3, 2019
    29
    v2 上对数据库的理解都这么次么。。。一个 select for update 就能解决的事情,扯这么多有的没的真是误导人
    wejaylyn
    wejaylyn
    Jun 6, 2019
    30
    unique index + select for update
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   3750 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 57ms · UTC 03:59 · PVG 11:59 · LAX 19:59 · JFK 22:59
    ♥ Do have faith in what you're doing.