【问题标题】:MySQL INSERT IF (custom if statements)MySQL INSERT IF(自定义 if 语句)
【发布时间】:2011-10-14 20:41:27
【问题描述】:

首先,这是问题的简明摘要:

是否可以有条件地运行INSERT 语句? 类似的东西:

IF(expression) INSERT...

现在,我知道我可以使用存储过程来做到这一点。 我的问题是:我可以在我的查询中这样做吗?


现在,我为什么要这样做?

假设我们有以下 2 个表:

products: id, qty_on_hand
orders: id, product_id, qty

现在,假设收到了 20 个巫毒娃娃(产品 ID 2)的订单。
我们首先检查手头是否有足够的数量:

SELECT IF(
    ( SELECT SUM(qty) FROM orders WHERE product_id = 2  ) + 20
    <=
    ( SELECT qty_on_hand FROM products WHERE id = 2)
, 'true', 'false');

然后,如果计算结果为 true,我们将运行 INSERT 查询。
到目前为止一切顺利。


但是,并发存在问题。
如果有 2 个订单正好同时进来,他们可能在其中任何一个进入订单之前都读取了现有数量。 然后他们都会下订单,从而超过qty_on_hand


所以,回到问题的根源:
是否可以有条件地运行INSERT 语句,以便我们可以将这两个查询合并为一个?

我搜索了很多,我能找到的唯一类型的条件INSERT 语句是ON DUPLICATE KEY,这显然不适用于这里。

【问题讨论】:

    标签: mysql concurrency insert if-statement race-condition


    【解决方案1】:

    我想使用值插入到表中,所以我找到了使用 IF 条件插入值的解决方案

    DELIMITER $$
    
    CREATE PROCEDURE insertIssue()
    BEGIN
        IF (1 NOT IN (select I.issue_number from issue as I where I.series_id = 1)) THEN
            INSERT IGNORE INTO issue ( issue_number, month_published, year_published, series_id, mcs_issue_id) VALUES (1, 1, 1990, 1, 1);
        END IF;
    END$$
    
    DELIMITER ;
    

    如果你以后想调用这个过程,那么简单

    CALL insertIssue()
    

    您可以在此site 中找到有关 PROCEDURES 和 if 条件的更多信息

    【讨论】:

      【解决方案2】:
      INSERT INTO TABLE
      SELECT value_for_column1, value_for_column2, ...
      FROM wherever
      WHERE your_special_condition
      

      如果选择没有返回任何行(因为您的特殊条件为假),则不会发生插入。

      使用问题中的架构(假设您的 id 列是 auto_increment):

      insert into orders (product_id, qty)
      select 2, 20
      where (SELECT qty_on_hand FROM products WHERE id = 2) > 20;
      

      如果手头没有足够的库存,这将不插入任何行,否则将创建订单行。

      顺便说一句,好主意!

      【讨论】:

      • @Bohemian:不需要两个SELECT 语句。一个就够了。看看我的回答。 :)
      • 没错,但我试图匹配我的 general 模式。不过我喜欢你的回答……+1
      • @Joseph Silber:做什么???那里的减法在哪里?减法与归一化有什么关系? :D 你知道上面的查询是由两个SELECT 语句组成的,而我的语句是由一个运行的? (没有反对你的答案,或者你,波西米亚人)。
      • @Shef:在您的代码中,在INSERT 之后,如果affected rows = 1,您将UPDATE ... qty_on_hand = qty_on_hand - 20。这就是您跟踪库存的方式。但是,我这样做的方式(参见上面的代码示例)是 qty_on_hand 永远不会改变。无论已售出多少,它始终设置为仓库中的库存数量。然后,当我想检查产品是否可用时,我将SUM(qty) FROM ordersqty_on_hand FROM products 进行比较。这只能通过子查询实现(当然,@Bohemian 也没有真正这样做,但就需要子查询而言,我们在同一页面上)。
      • @Joseph:你说的是数据库规范化,现在你回来的数据不一致?处理任务的伙伴,在订购时进行求和的开销远远超过更新记录的开销。 ;)
      【解决方案3】:

      试试:

      INSERT INTO orders(product_id, qty)
      SELECT 2, 20 FROM products WHERE id = 2 AND qty_on_hand >= 20
      

      如果存在id 等于2 的产品,并且此产品的qty_on_hand 大于或等于20,则将使用值product_id = 2qty = 20 进行插入。否则,不会发生插入。

      注意:如果您的产品 ID 是唯一的,您可能需要在 SELECT 语句的末尾添加一个 LIMIT 子句。

      【讨论】:

      • 你会如何否定这个?
      • 一种方法是在末尾添加以下内容:HAVING COUNT(*) = 0。
      【解决方案4】:

      不确定并发性,您需要阅读 mysql 中的锁定,但这可以确保如果 20 个项目可用,您只需要 20 个项目:

      update products 
      set qty_on_hand = qty_on_hand - 20 
      where qty_on_hand >= 20
      and id=2
      

      然后您可以检查有多少行受到影响。如果没有受到影响,则说明您没有足够的库存。如果有 1 行受到影响,那么您已经有效地消耗了库存。

      【讨论】:

        【解决方案5】:

        您可能以错误的方式解决问题。

        如果您担心两个读取操作会同时发生,因此一个会处理陈旧数据,解决方案是使用锁或事务

        让查询这样做:

        • 锁定表以供读取
        • 读表
        • 更新表
        • 释放锁定

        【讨论】:

        • 我不太确定锁定是最好的解决方案。这可能会导致严重的性能问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-10
        • 2012-06-05
        • 2012-04-12
        • 2016-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多