【问题标题】:Is this mysql thread-safe?这是mysql线程安全的吗?
【发布时间】:2020-03-30 09:27:37
【问题描述】:

我有一个包含以下列的表格:id(int), wallet_id (int), amount (int), recharge_date (timestamp), status (int)status 表示挂起(0)、成功(1) 或失败(2)。我需要在充值之前确保当月充值的总金额不超过10,000。我还需要涵盖同时有 2 个或更多充电请求的场景。

我认为以下步骤可以帮助避免竞争条件:

  1. 插入状态为 0(待处理)的表中,获取 wallet_id 说“1234”
  2. 从其中 wallet_id = 1234 AND month(recharge_date) = 当前月份和 (0,1) 中的状态的表中选择 sum(amount)
  3. 如果此总和超过 10,000,则将状态更新为 2(失败)并返回。
  4. 执行一些应用程序级别的操作,然后将状态更新为 1(成功)或 2(失败)

这些步骤是否确保线程安全?另外,mysql中还有其他更简单的方法来确保这个用例的线程安全吗?

编辑: 解释实际流程以更清晰:

  1. 服务器收到请求为某个 wallet_id(比如 99)添加 500 的金额。
  2. 插入一个处于待处理状态的新订单:INSERT INTO orders VALUES(wallet_id = 99,amount = 500,status = pending)
  3. 需要保证一个月最多可以充值10000。所以 SELECT SUM(amount) WHERE wallet_id = 99 AND month=current_month AND status IN(pending, success)
  4. 如果步骤 3 中的总和 >= 10,000,则将步骤 2 中创建的订单状态更新为失败。返回
  5. 如果总和小于 10,000,则执行其他操作,返回并更新在第 2 步中创建的订单以成功或失败。

如果此流同时收到多个请求,则会出现混乱。

【问题讨论】:

    标签: mysql concurrency transactions


    【解决方案1】:

    很好地抓住了并发性。 Database transactions 就是为此而生的。

    您可以通过这样的交易来做到这一点。 (未调试)

    BEGIN TRANSACTION;
    
    SELECT SUM(amount) INTO @sum
      FROM mytable
     WHERE status IN (0,1)
       AND recharge_date >= LAST_DAY(CURDATE()) + INTERVAL 1 DAY - INTERVAL 1 MONTH
       AND recharge_date < LAST_DAY(CURDATE()) + INTERVAL 1 DAY 
       AND wallet_id = (((whatever))
       FOR update;
    
    UPDATE mytable SET status=2
     WHERE status IN (0,1)
       AND recharge_date >= LAST_DAY(CURDATE()) + INTERVAL 1 DAY - INTERVAL 1 MONTH
       AND recharge_date < LAST_DAY(CURDATE()) + INTERVAL 1 DAY 
       AND wallet_id = (((whatever))
       AND @sum > 1000;
    
    /* then do your application logic, and come back and do */
    
    UPDATE mytable SET status=(((whatever)))
     WHERE status IN (0,1)
       AND recharge_date >= LAST_DAY(CURDATE()) + INTERVAL 1 DAY - INTERVAL 1 MONTH
       AND recharge_date < LAST_DAY(CURDATE()) + INTERVAL 1 DAY 
       AND wallet_id = (((whatever))
       AND @sum > 1000;
    
    /* finally, commit your transaction. */
    COMMIT;
    

    注意SELECT...FOR UPDATE 和两个UPDATE 语句都具有相同的WHERE 语句。这很重要,因为您必须避免更新未选择更新的行。完成后,COMMIT 交易。如果您决定在BEGIN TRANSACTION 之后的任何时间都不想继续,您可以ROLLBACK 交易,事情会回到您开始之前的状态。

    这种模式

       AND recharge_date >= LAST_DAY(CURDATE()) + INTERVAL 1 DAY - INTERVAL 1 MONTH
       AND recharge_date < LAST_DAY(CURDATE()) + INTERVAL 1 DAY 
    

    是在当前日历月中选择日期的最佳方式。它允许使用列上的索引来搜索相关记录,而MONTH(recharge_date) = MONTH(CURDATE()) 则没有。

    我对一件事有点困惑。您说您需要进行 SUM,这表明您的表中的多个记录受到您预期的 status 更改的影响。这真的是你想要的吗?

    【讨论】:

    • 已编辑以更清楚地说明您的上一个问题。很高兴知道有关在“recharge_date”上使用索引的信息。
    猜你喜欢
    • 1970-01-01
    • 2015-10-13
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多