V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
nerkeler
V2EX  ›  MySQL

mysql 多条 update 语句怎么保证同时成功同时失败呢?

  •  
  •   nerkeler · 2023-12-06 16:05:55 +08:00 · 3510 次点击
    这是一个创建于 384 天前的主题,其中的信息可能已经有所发展或是发生改变。

    mysql 多条 update 语句怎么保证同时成功同时失败呢?

    java 分布式 springcloud项目 数据库是mysql,我现在有一张产品库存表, 里面有多个产品,每行记录了这个产品的详情( ID 代码 名称 类型 库存量) 产品库存表 同一个产品只会存在一条记录。 产品库存表 大致结构

    create table 产品库存表 (
    id ..
    产品代码..
    产品名称..
    产品类型..
    库存量..
    是否有效
    修改时间
     创建时间
    )
    

    现在前端页面有个操作,大致就是将这些产品 分到一个产品包里 这个产品包 就是一个选择了不同产品和其数据的产品集合 产品包分配表另建的一张表

    create table 产品包分配统计表(
    id 
    产品包名称
    产品包 ID
    创建时间
    是否有效
    )
    

    产品包明细表

    create table 产品包分配统计表(
    id 
    产品名称,
    产品 ID ,
    产品分配数量。
    产品包名称
    产品包 ID
    创建时间
    是否有效
    )
    

    简而言之,就是从 创建一个产品包 包含多个产品, 自己指定分配的产品数量,

    所以 我从 产品的主库存 扣除分配给产品包的产品库存的时候, 需要保证这些产品修改 同时成功和失败,这样我好控制并发。

    update 产品表    set 产品库存 =   产品库存 - 分配数量 where 产品库存 > 分配数量
    

    这是一条产品更新的 语句,我现在想让 这些选了的产品 更新操作 同时成功,或者同时不执行,

    我尝试 将多个 update 放在一行执行

    update 产品表  set 产品库存  = case 条件(产品类型) when  xxx  then  对应的数量  
    when xxx then 对应的产品数量 
    ...
    end
    when  产品库存 > case 产品类型  when  xxx  then 对应的数量
    
    

    update 语句 条件不成立的时候 只会 让影响的行数 是 0 ,从而让满足条件的执行了,没满足的没执行。

    各位有什么好办法吗

    第 1 条附言  ·  2023-12-07 19:09:35 +08:00

    我统一回复下吧,

    1. 我并不是不知道事务的概念,平时使用都是在sql异常的情况下被动回滚数据,因为update操作 在这里 我认为的不成功是 没有更新成功,也就是受影响的行数是 0 ,并没有产生SQL异常。让我一时没有想到去主动控制。
    2. 另外就是我在想这个问题的时候更多的去想怎么在这个负责的业务逻辑上解决并发的问题。有当局者迷的感觉,另外对事务包括数据库的很多东西老实说确实没有深入或者说全面的了解。
    3. 然后对耐心提供建议的老师表示感谢,跳出结果来看这个问题,确认很容易,我相信大家都有 被一个小问题卡住脚步的时候,回头看发现这只是个简单的问题,没必要冷嘲热讽吧。
    4. 还有,不是科班,但不是没接触编程,大二自学python (爬虫,GUI) ,大三专业课单片机,毕业设计就是自己写的单片机的C程序,数据库倒是这两年 写java 才有所接触,毕竟java好转
    36 条回复    2023-12-08 11:46:45 +08:00
    goodryb
        1
    goodryb  
       2023-12-06 16:10:27 +08:00   ❤️ 1
    以我不多的数据库认知, 你标题的需求应该会用到 事务
    NoobNoob030
        2
    NoobNoob030  
       2023-12-06 16:11:49 +08:00
    触发关键词 事务
    ysnow888
        3
    ysnow888  
       2023-12-06 16:13:59 +08:00
    cyrivlclth
        4
    cyrivlclth  
       2023-12-06 16:14:08 +08:00
    关键词,事务,隔离级别。。。
    nerkeler
        5
    nerkeler  
    OP
       2023-12-06 16:17:25 +08:00
    @cyrivlclth msyql 隔离级别 没有权限修改, update 不满足条件的语句不会产生异常,只是 受影响的行数是 0
    jowan
        6
    jowan  
       2023-12-06 16:22:06 +08:00
    突击抽查 事务的四大特性 请作答
    258
        7
    258  
       2023-12-06 16:22:50 +08:00
    事务 or 分布式事务?
    nerkeler
        8
    nerkeler  
    OP
       2023-12-06 16:23:22 +08:00
    不满足的 条件的 udpate 语句 受影响的行数是 0 的情况下 你们的意思会触发 事务?
    jowan
        9
    jowan  
       2023-12-06 16:26:36 +08:00
    并发不大不考虑性能的话 你直接按正常逻辑写
    事务里面加行锁 就行了 不用这么复杂
    javalaw2010
        10
    javalaw2010  
       2023-12-06 16:28:46 +08:00   ❤️ 1
    要么,你一条一条来,当某个 update 的的影响行数是 0 时,你 rollback ,要么,你有一个预期有多少行会受影响,如果实际影响的行数与预期不符,你 rollback 。异常这种东西一般发生在你代码里,跟数据库有什么关系。
    nerkeler
        11
    nerkeler  
    OP
       2023-12-06 16:30:03 +08:00
    @jowan 更新的是多个产品记录,并不是一条记录,你这个思路要加表锁,比如 产品 1 减库存成功了,产品 2 减库存也成功了, 但是产品 3 不满足更新的条件,这时候需要把前两个更新操作回撤
    nerkeler
        12
    nerkeler  
    OP
       2023-12-06 16:31:40 +08:00
    @javalaw2010 明白你的意思了,通过受影响的行数 判断,手动抛异常
    jowan
        13
    jowan  
       2023-12-06 16:32:59 +08:00
    @nerkeler 对呀 transaction 里面 如果业务逻辑不符合的话 你主动 rollback 就行了啊 不就是这样用的吗
    Plutooo
        14
    Plutooo  
       2023-12-06 16:33:27 +08:00
    这不是一个代码里 @Transaction 就解决的事情吗,要么手动抛异常,要么手动回滚
    nerkeler
        15
    nerkeler  
    OP
       2023-12-06 16:35:27 +08:00
    学艺不精,谢了各位
    jowan
        16
    jowan  
       2023-12-06 16:38:11 +08:00
    @Plutooo 他这个就是为了保证库存一致性的 并发不大的情况
    @Transaction 后 可以按正常逻辑顺序来写 SQL
    比如先减产产品库存表 查出来后 for update 加锁 再去加产品包表的库存
    不需要 case when
    buxudashi
        17
    buxudashi  
       2023-12-06 16:49:40 +08:00
    事务开启;

    结果=false;
    do{
    执行 1 成功往下走,不成功 break;
    执行 2 成功往下走,不成功 break;
    ......
    结果=true;
    }while(false);//保证只执行 1 次。

    如果 true commit;
    如果 false callback;
    8355
        18
    8355  
       2023-12-06 16:53:15 +08:00
    还好是 java 开发 如果是 php 的估计要被喷死了😀
    nerkeler
        19
    nerkeler  
    OP
       2023-12-06 16:54:11 +08:00
    @buxudashi 老哥你这个能简化吧
    事务开启;

    产品 list.foreach(xxx ->
    执行 xxx 不成功 callback;
    )


    commit;
    xiaoHuaJia
        20
    xiaoHuaJia  
       2023-12-06 17:07:49 +08:00
    如果请求量不高就事务 。是如果是高并发场景 redis 加 lua 脚本
    uleh
        21
    uleh  
       2023-12-06 17:30:14 +08:00
    看了标题我以为是海量数据分布式更新还要保障高并发
    进来一看。。。
    nerkeler
        22
    nerkeler  
    OP
       2023-12-06 17:48:56 +08:00
    @uleh 让你失望了
    ZField
        23
    ZField  
       2023-12-06 17:54:56 +08:00
    库存问题啊,省事的方案就是提到 redis 上面来处理 ,缓存都处理完了再落库。
    直接 MySQL 的话,单机就锁库存,然后开启事务,手动判断,手动回滚。
    totoro52
        24
    totoro52  
       2023-12-06 18:14:20 +08:00
    判断是不是更新条数 0 0 就直接抛异常不就好了 剩下的都不走了 然后交给分布式事务去处理即可。
    jackaluo
        25
    jackaluo  
       2023-12-06 18:20:45 +08:00
    chatgpt 的回复:
    在多个并发更新操作中,确保同时成功或同时失败可以使用事务来实现。在 MySQL 中,您可以使用事务来包装多个 UPDATE 语句,以便它们要么全部成功,要么全部失败。这样可以确保数据的一致性。

    以下是一个示例 Java Spring 代码,用于在分布式 Spring Cloud 项目中使用事务来实现这种需求:

    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    @Service
    public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
    this.productRepository = productRepository;
    }

    @Transactional
    public boolean updateProductStocks(List<ProductUpdateRequest> updateRequests) {
    try {
    for (ProductUpdateRequest updateRequest : updateRequests) {
    // 执行产品库存更新操作
    int rowsUpdated = productRepository.updateProductStock(updateRequest.getProductId(), updateRequest.getQuantity());
    if (rowsUpdated != 1) {
    throw new RuntimeException("Failed to update product stock");
    }
    }
    return true; // 所有更新成功
    } catch (Exception e) {
    // 发生异常,回滚事务
    return false; // 任何一个更新失败都会导致整个事务失败
    }
    }
    }
    4ark
        26
    4ark  
       2023-12-06 19:49:27 +08:00 via iPhone
    @jackaluo 在这里贴 ChatGPT 的回复会被永久 ban
    jackaluo
        27
    jackaluo  
       2023-12-06 22:05:52 +08:00
    @4ark 还有这种说法。。。那我删帖吧
    buxudashi
        28
    buxudashi  
       2023-12-07 08:23:23 +08:00
    @nerkeler 不能简化。你的 foreach 里会全部执行。比如执行 10 次,第 3 次失败。你第 4 次和后面的循环已经没意义。后面的 commit 更加没意义。

    正常情况下要退出后后 fallback.或者集体 commit.
    siweipancc
        29
    siweipancc  
       2023-12-07 08:48:06 +08:00 via iPhone
    坏了,事务都没学,你没上大学?
    nerkeler
        30
    nerkeler  
    OP
       2023-12-07 09:04:50 +08:00 via Android
    @buxudashi 我是使用抛异常的方式触发会滚,java 里这么写应该没问题
    nerkeler
        31
    nerkeler  
    OP
       2023-12-07 09:05:50 +08:00 via Android
    @siweipancc 你要和我探讨探讨机械原理?
    luomao
        32
    luomao  
       2023-12-07 09:09:27 +08:00
    楼主快说你是开发 node 的
    xwayway
        33
    xwayway  
       2023-12-07 09:16:16 +08:00
    @siweipancc 所以不是说看不起非科班,而是非科班……。完了说这句话我要被喷死,但是我还是想说
    shea
        34
    shea  
       2023-12-07 09:36:50 +08:00
    try 再加事务,还好你是用的 java ,要是 php ,现在你这帖已经是热帖了。
    cyrivlclth
        35
    cyrivlclth  
       2023-12-07 15:50:56 +08:00
    @xwayway 我非科班哈,这种基础知识只能说这行门槛太低了。
    julyclyde
        36
    julyclyde  
       2023-12-08 11:46:45 +08:00
    按说不应该存在 affected 为 0 的情况吧?
    是不是应该先 select for update 锁上然后再 update ?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3003 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 13:22 · PVG 21:22 · LAX 05:22 · JFK 08:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.