V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Niner
V2EX  ›  Java

update 大家会允许这样写吗?

  •  
  •   Niner · 16 天前 · 6082 次点击
    User u = userMapper.selectOne(id);
    u.setName(myname);
    u.setAge(myAge);
    userMapper.updateById(u);
    

    我看工作里面很多代码都是这样更新的,虽然方便但是有并发问题,大佬们 cr 遇到会打回去吗?

    73 条回复    2025-02-05 17:33:22 +08:00
    kanepan19
        1
    kanepan19  
       16 天前
    看场景,订单这样去更新要被批。
    用户这样更无所谓。
    ncbdwss
        2
    ncbdwss  
       16 天前
    绝对不允许。
    KongLiu
        3
    KongLiu  
       16 天前
    并发问题,建议加上乐观锁
    SingeeKing
        4
    SingeeKing  
       16 天前 via iPhone
    不懂 java ,但为啥这样会有并发问题?按我的理解 u 是 select 返回的,updateById 的过程才会创建 transaction 并 commit ,理论上 u 不共享不会出并发问题?
    MooRider
        5
    MooRider  
       16 天前
    @SingeeKing #4 第一个请求还没完成的时候第二个请求进来了, 由于一些原因导致第二个请求比第一个快, 最后的结果就变成了保存第一个请求的值
    sduoduo233
        6
    sduoduo233  
       16 天前 via Android
    @SingeeKing 我也不懂 java ,但是我觉得如果在 select 和 update 之间 user 的某一项数据更新了,会被 update 覆盖
    zhouhu
        7
    zhouhu  
       16 天前
    @kanepan19 大佬
    pelloz
        8
    pelloz  
       16 天前   ❤️ 5
    看场景,没要求的位置这么写没问题,写完赶紧下班。有要求的位置简单加一个事务,赶紧下班。对性能有影响了?那就改为乐观锁,改完下班。
    crysislinux
        9
    crysislinux  
       16 天前 via Android
    所以你们的每个表都有类似 version 字段的东西么,实践中很少这样吧,我看到的绝大部分更新都是这样更的。
    gadfly3173
        10
    gadfly3173  
       16 天前
    理论上最好每次只更新自己的字段,但是那样写真的很麻烦吧。要通用的话你就得改造你的 DO ,让他能检测自己被改变了什么字段。
    csys
        11
    csys  
       16 天前   ❤️ 1
    真要说起来,这才是“正确”的(或者说是现代的)写法,前提是有乐观锁或者分布式锁

    业务系统用数据库事务来做并发控制是个很不好的做法

    参考 DDD 中的 repository 和 unit of work 模式

    https://martinfowler.com/eaaCatalog/repository.html
    https://martinfowler.com/eaaCatalog/unitOfWork.html
    user8341
        12
    user8341  
       16 天前
    用户资料(姓名、年龄)不就是他自己一个人会更改吗?应该不会有并发问题。
    dcsuibian
        13
    dcsuibian  
       16 天前 via Android   ❤️ 1
    并发你这后端改了也没啥用

    实际上最有可能出现的并发问题不在这两行之间。而是前端的问题
    比如
    1 、甲打开了网页,拿到了 a 和 b 和两个字段,然后就去忙别的去了
    2 、乙也打开了网页,拿到了 a 和 b ,然后把 b 修改成 b1 ,接着把 a 和 b1 一起提交上去
    3 、甲回来了,把 a 修改成了 a1 ,然后把 a1 和 b 一起提交上去了
    4 、最终就输 a1 和 b ,b1 的修改丢失了

    正常前端都不会只传变更了的字段,而是偷懒一股脑都传上来
    所以你后端其实根本判断不了

    不过这其实也没啥大问题,因为通常来说一个表单的正确性就是取决于最后修改他的人而已
    既然最后修改他的人觉得没问题,那其实也确实没啥大问题
    xuanbg
        14
    xuanbg  
       15 天前
    @SingeeKing 会出现你更你的我更我的,大家的原始数据都一样,先更的被后更的还原。
    chendy
        15
    chendy  
       15 天前
    首先,一般没有 CR ,提了测了上了完事
    然后,除非并发问题会造成生命财产损失或者大量用户投诉,否则无需在意,更何况是一些并发极少的场景
    isnullstring
        16
    isnullstring  
       15 天前
    不懂 JAVA
    要么先开事务,要么条件里带上原来的值,保证绝对更新
    Foxkeh
        17
    Foxkeh  
       15 天前
    select for update 行锁
    Ayanokouji
        18
    Ayanokouji  
       15 天前
    楼主是想问,patch or update 吧,java 一般都是全字段更新(过滤 0 值)。
    最好是 new 对象,set 对应值,而不是 select by Id ,然后 set 。
    不过,无并发冲突,两种都没问题。
    但是如果有并发,两种都有问题,都得加锁。
    gbw1992
        19
    gbw1992  
       15 天前
    我想了一下,这样的问题除了给数据加上版本号貌似没什么特别好的处理办法了
    我觉得还是得看业务,如果这个数据多人共享且并发两很高,那必须加锁
    如果没这个需求应该无所谓
    angryfish
        20
    angryfish  
       15 天前
    严格来说这样是有问题的。
    比如你这个场景 1 逻辑是修改了 name,age 。如果另外的场景 2 是修改用户的 phone,并且 phone 修改在前,那么这个 phone 的修改会被场景 1 的旧 phone 覆盖。
    oracleHe
        21
    oracleHe  
       15 天前 via Android
    以面向对象的思维来说一般都是这么写的。因为往往在实际业务中你把对象 select 出来除了赋值可能还会做很多业务逻辑和计算,完了就将整个对象 update 。如果在要求比较高的场景下,要解决并发问题就通过分布式锁来处理。

    当然假如只是单纯更新数据的场景,这种写法并不好,因为可能只要更新一个字段却执行了两个 sql ,甚至把所有字段都更新了一遍,效率比较低,也存在你说的并发问题。所以简单的更新数据比较建议直接通过一句简单的 update 更新。
    oldking24
        22
    oldking24  
       15 天前
    分场景呢,如果真的并发高那就加锁,要么乐观锁 version 字段,要么加分布式锁,按理说,用户不会天天修改自己的用户信息。
    xuanbg
        23
    xuanbg  
       15 天前
    @dcsuibian 严格来说,不加锁的更新都有可能出你说的问题。加锁带来的问题是性能下降,和用户交互不友好。
    但这个问题可以通过业务规则规避,只允许一个用户修改数据就啥问题都没了。
    spritecn
        24
    spritecn  
       15 天前
    没啥 大影响 ..很多代码这么写的,虽然新开一个 userForUpdate 类会清淅一点
    并不并发的,没区别,就算你只更新你的,并发你不加锁,开不开新类也控制不了
    对于阿里/腾讯 这类优化过的 mysql,会自动过滤不需要更新的字段,对性能影响也不大
    Vegetable
        25
    Vegetable  
       15 天前
    所以,问题出在 mapper 的 updatebyid 并不会像符合认知的 orm 行为那样,只修改自己变更过的字段。而是会 update 整个对象除了 ID 外所有的字段。

    对不熟悉的人来说,这个还挺隐蔽的,但是想必已经是 java 生态内的常识了吧。
    gerefoxing
        26
    gerefoxing  
       15 天前
    只更新需要更新的字段,全部更新没意义,重新 new 个对象更新
    cornorj6
        27
    cornorj6  
       15 天前
    spring data jpa 中可以给实体加个 @Version 就能自动处理乐观缩,这种方式没有毛病,只有更新的一瞬间判断,感觉效率比行锁高。
    yor1g
        28
    yor1g  
       15 天前
    那怎么写才正确呢 🤣 每个更新操作都考虑并发么
    chobitssp
        29
    chobitssp  
       15 天前
    ef 使用 ChangeTracker 跟踪状态变化 只会更新变化的字段
    java 的 orm 不清楚是否支持
    wei2629
        30
    wei2629  
       15 天前
    除非需要事务, 不然我觉得这就是最好的选择。
    cathub86
        31
    cathub86  
       15 天前
    局部变量这样写 没事吧
    jnliyan1
        32
    jnliyan1  
       15 天前
    查询是不是多余了
    cenbiq
        33
    cenbiq  
       15 天前
    如果你用 C# ef orm ,官方就是推荐这么写的,但是 ef 自带乐观锁支持和 patch 更新,改了什么字段就更新什么字段
    carytseng
        34
    carytseng  
       15 天前
    这是 mysql 经典的丢失更新问题,举个相似例子,a 用户读取 user1 时 age=0 并修改 age=age+1=1 ,b 用户读取 user1 时 age=0 并修改 age=age+2=2 ,此时丢失了 a 用户的修改,中间其实更新了+1 ,所以处理时如果要满足场景你在读前就要加锁
    YepTen
        35
    YepTen  
       15 天前   ❤️ 2
    某些场景下,绝对允许;
    某些场景下,绝对不允许;
    某些场景下,无所屌谓;
    所以,你是哪个场景?
    wuhunyu
        36
    wuhunyu  
       15 天前   ❤️ 1
    使用乐观锁更新的话,如果出现了并发有概率出现部分用户无法更新成功的情况,也就是数据库返回受影响行数为 0 ,这个时候需要提示用户修改失败,并要求重新刷新表单数据之后重新提交修改,并发量大的情况下,用户仍然有可能第二次提交也是失败的

    使用锁的话,大概率是可以更新成功的,但可能遇到的问题有,并发量大时可能会等待,表现为用户提交表单等待的时间会长一点或者直接等待超时(这种情况下,有可能修改成功,也可能失败)。此外还有一个问题是,后提交的用户有可能会把之前提前的信息给覆盖掉(比如 #13 提到的问题)

    如果按照锁升级策略来看的话,是否可以考虑如下策略
    默认使用乐观锁,当出现冲突时,也就是数据库返回受影响行数为 0 时,进入重锁模式,重锁模式释放完毕之后,再做一个判断,如果当前没有使用资源的线程,则切换回乐观锁模式
    Pursue9
        37
    Pursue9  
       15 天前
    用 MP 的 LambdaUpdateWrapper ,只更新指定字段
    MoYi123
        38
    MoYi123  
       15 天前
    没并发问题吧, 这里只是用了一个比较蠢的办法去构造了 User u 而已.
    pangdundun996
        39
    pangdundun996  
       15 天前
    为啥不按需更新?
    ```java
    User u= new User();
    u.setId(id);
    u.setName(myname);
    u.setAge(myAge);
    userMapper.updateById(u);

    ```
    zzNaLOGIC
        40
    zzNaLOGIC  
       15 天前
    就算是生成器生成的 mapper ,都有 updateByPrimaryKeySelective 吧
    用一下又不麻烦,这样就是纯懒到家了。
    chengyiqun
        41
    chengyiqun  
       15 天前
    我知道 python 的 flask 在
    u.setName(myname);
    u.setAge(myAge);
    之后就可以直接 commit 了,
    java 好像真没啥好办法, 想要性能好还要加锁就只能乐观锁了.
    binge921
        42
    binge921  
       15 天前
    我感觉 v 站最多的就是 java 选手了,哈哈哈哈
    angryfish
        43
    angryfish  
       15 天前
    @binge921 #42 对于业务代码,除了 java 没哪个语言能打的。这也侧面反映,写业务逻辑的人占据了大部分的。
    chandlerbing9317
        44
    chandlerbing9317  
       15 天前
    和语言没关系,对于先 check 后操作的场景就是典型的竞态条件,一般要加锁。但也分场景,比如上面说的订单场景,如果每条彼此有影响,比如先 check 是否为 0 ,更新的时候再原记录-1 肯定要加锁。如果用户资料修改,更改不依赖之前数据,那加不加都无所谓,纠结什么谁覆盖了谁的更新,谁先谁后没有任何意义。网络本身都可能有波动,不一定先请求的用户就一定先收到,也不代表一定先处理完。
    lyxxxh2
        45
    lyxxxh2  
       15 天前
    我写 php 的,感觉还好。
    但是不需原数据做逻辑,查询就多余了。

    看到有说全部更新?
    应该不会吧,orm 会跟原数据对比,只更新改变的字段。

    字符串并发有什么影响??? 又不是金额之类的数字。
    就算是金额,你也要查询出来做校验啊!
    你这代码场景,我看不出有并发问题。
    maximdx
        46
    maximdx  
       15 天前
    这个得看 orm 的实现还有配置?
    有的 orm 更新可以提供 TX 的
    lyxxxh2
        47
    lyxxxh2  
       15 天前
    @Vegetable
    我擦,刚注意到你这个描述了。
    这已经是 bug 了!
    这么写,只能说脑*了。
    RandomJoke
        48
    RandomJoke  
       15 天前
    user 场景来说,一般没什么问题,基本不会有什么并发。但这种全字段更新的,乐观锁控制下会减少点并发的问题。JPA 有自动乐观锁,不一定在这里体现。
    lingyumin1997
        49
    lingyumin1997  
       15 天前
    这里并发会有数据安全的问题。
    如果不是业务场景迫不得已要先查询再根据查询的数据进行更新,不要这么写,直接 update 就好。
    有些业务情况下,有时必须要先查询再进行更新,这种情况建议加锁(乐观锁也好、其他方式加悲观锁也好)。
    NoKey
        50
    NoKey  
       15 天前   ❤️ 1
    这个不能加 where 条件么?不满足条件不更新,就不会更新到已经被人更新过的数据了?
    Cyron
        51
    Cyron  
       15 天前 via iPhone
    一楼已经给出答案
    horizon
        52
    horizon  
       15 天前
    我不懂 java
    java 中没有原子操作吗
    bronyakaka
        53
    bronyakaka  
       15 天前
    并发问题很大,必须上锁。
    1194129822
        54
    1194129822  
       15 天前
    先读后写的情况下不都是事务了,没有并发问题。难带这种代码还没事务的吗。
    irisdev
        55
    irisdev  
       15 天前
    哪有那么多并发问题..一般不会更新别人的数据吧,更新自己的数据客户端卡下,偶尔出问题运维下好了
    Configuration
        56
    Configuration  
       15 天前
    1. 加锁
    2. 用 repeatable_read 事务

    但是不管哪种方式都或多或少影响性能
    bitmin
        57
    bitmin  
       15 天前
    python 的 sqlalchemy 就超级无语,select 的时候就自动开启事务,还关闭不了
    iv8d
        58
    iv8d  
       15 天前
    根据你业务量决定,在一个就是关于金融积分等要避免,其他无所谓吧
    8355
        59
    8355  
       15 天前
    取决于你前置请求是否有类似防抖的操作,如果有校验登陆状态和防抖并发的情况这代码本身并没有什么问题。
    登陆状态都合法,也都是用户自己操作的,非敏感数据(金额扣减/状态变更等等)并不需要加太多限制,不然除了降低性能提高复杂度之外没有任何正向收益。
    JasperWong
        60
    JasperWong  
       15 天前
    绝对打回
    lyy780808
        61
    lyy780808  
       15 天前
    分情况吧。
    如果需要处理的话可以用事务+悲观锁或者直接乐观锁来处理。
    UmiKz
        62
    UmiKz  
       15 天前
    从功能上来说,就是根据 ID 去更新相关信息,多次短时间内同时调用这是使用场景,跟这个功能没关系。
    zhady009
        63
    zhady009  
       15 天前
    完全是看实际场景来决定的,user 这种数据这样写一般没问题,就算数据被覆盖问题也不大,订单这种就要注意这种问题
    layxy
        64
    layxy  
       15 天前
    不允许也不建议这样搞
    s1461a
        65
    s1461a  
       15 天前
    @MooRider 这个 user 是局部变量哪来的并发问题
    Leviathann
        66
    Leviathann  
       15 天前
    这不是 spring data 的标准写法么
    Leviathann
        67
    Leviathann  
       15 天前
    看了下上面的,原来用 mybatis 的大部份表连 version 没有?
    ForkNMB
        68
    ForkNMB  
       14 天前
    没问题啊 人家说不定方法上有 redis 分布式锁的注解
    zzsong
        69
    zzsong  
       14 天前
    这是 spring-data 的标准写法,表里会加 version 字段的。
    mmdsun
        70
    mmdsun  
       14 天前
    mybatis plus 也有乐观锁啊 看下 userMapper.updateById(u); 会不会触发乐观锁 @Version 标记的字段
    chrisia
        71
    chrisia  
       9 天前
    你这个查出来再更新肯定有并发问题,直接 updateById 只更新必要字段就好,单个 update 是能保证原子性的。否则的话要用乐观锁/事务来控制。
    dbolo123
        72
    dbolo123  
       6 天前
    @csys 赞同,写法是正确的,但是缺少一个真正实现了 unit of work 的框架支撑,而 jpa 就是那个实现了 unit of work 的,同样的写法,他会跟快照对比,看哪个字段变更了,最终生成 update 语句只 update 变更的,不需要加锁
    house600
        73
    house600  
       2 天前
    @ForkNMB #68 我也想这么问,要结合上下文来说吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   942 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 20:30 · PVG 04:30 · LAX 12:30 · JFK 15:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.