PHP / MySQL - 如何防止两个请求*更新

时间:2013-02-22 14:51:20

标签: php transactions

我有一些问题...... 例如:用户将为他的美元购买东西

  1. 检查他的美元余额
  2. 从他的帐户中扣除美元
  3. 下订单 - >订单队列
  4. 用户获得他的物品,另一个获得他的美元
  5. 让我们说,用户在同一秒内(非常快)发出5个请求。 因此,有可能(并且正在发生)5个请求正在运行。 他只有1个请求才能购买。现在请求 是如此之快,脚本检查他的平衡,但不是那么快,它扣除了 来自他账户的钱。所以请求将通过两次! 怎么解决?

    在开始这个过程之前,我在mysql中使用LOCK:

    1. IS_FREE_LOCK - 如果没有,请检查此用户是否有锁定 - > 2。
    2. GET_LOCK - 设置锁定
    3. 下订单/交易
    4. RELEASE_LOCK - 释放锁
    5. 但这并没有真正起作用。还有另一种方式吗?

      function lock($id) {
        mysql_query("SELECT GET_LOCK('$id', 60) AS 'GetLock'");
      }
      
      function is_free($id) {
        $query = mysql_query("SELECT IS_FREE_LOCK('$id') AS 'free'");
        $row = mysql_fetch_assoc($query);
        if($row['free']) {
          return true;
        } else {
          return false;
        }
      }
      
      function release_lock($id) {
        mysql_query("SELECT RELEASE_LOCK('$id')");
      }
      
      function account_balance($id) {
        $stmt = $db->prepare("SELECT USD FROM bitcoin_user_n WHERE id = ?");
        $stmt->execute(array($id));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
      
        return $row['USD'];
      }
      
      if(is_free(get_user_id())) {
        lock(get_user_id());
        if(account_balance(get_user_id()) < str2num($_POST['amount'])) {
          echo "error, not enough money";
        } else {
          $stmt = $db->prepare("UPDATE user SET USD = USD - ? WHERE id = ?");
          $stmt->execute(array(str2num($_POST['amount']), get_user_id()));
          $stmt = $db->prepare("INSERT INTO offer (user_id, type, price, amount) VALUES (?, ?, ?, ?)");
          $stmt->execute(array(get_user_id(), 2, str2num($_POST['amount']), 0));
      }
      

      更新 使用SELECT ... FOR UPDATE测试事务函数

      $db->beginTransaction();
      $stmt = $db->prepare("SELECT value, id2 FROM test WHERE id = ? FOR UPDATE");
      $stmt->execute(array(1));
      $row = $stmt->fetch(PDO::FETCH_ASSOC);
      if($row['value'] > 1) {
        sleep(5);
        $stmt = $db->prepare('UPDATE test SET value = value - 5 WHERE id = 1');
        $stmt->execute();
        $stmt = $db->prepare('UPDATE test SET value = value + 5 WHERE id = 2');
        $stmt->execute();
        echo "did have enough money";
      } else {
        echo "no money";
      }
      $db->commit();
      

5 个答案:

答案 0 :(得分:25)

首先,您必须使用交易,但这还不够。在您的交易中,您可以使用SELECT FOR UPDATE

基本上说,“我要更新我正在选择的记录”,所以它设置了UPDATE设置的相同锁。但请记住,这必须在关闭自动提交的事务中发生。

答案 1 :(得分:6)

使用TRANSACTION如果失败,您可以回滚。

例如,假设当前余额为20美元。

Connection A               Connection B
=======================    ===========================
BEGIN TRANSACTION         
                           BEGIN TRANSACTION
SELECT AccountBalance  
                           SELECT AccountBalance
--returns $20
--sufficient balance,
--proceed with purchase
                           --returns $20
                           --sufficient balance,
                           --proceed with purchase

                            --update acquires exclusive lock
                           UPDATE SET AccountBalance
                              = AccountBalance - 20
--update blocked due
UPDATE SET AccountBalance
  = AccountBalance - 20

                           --order complete
                           COMMIT TRANSACTION

--update proceeds

--database triggers
--constraint violation
--"AccountBalance >= 0"

ROLLBACK TRANSACTION

答案 2 :(得分:5)

这是我多年前做过的事情..

results = query("UPDATE table SET value=value-5 WHERE value>=5 AND ID=1")
if (results == 1) YEY!

(这仍然是一种可靠的方法吗?)

答案 3 :(得分:1)

您需要在SERIALIZABLE隔离级别使用TRANSACTION。

答案 4 :(得分:0)

您需要使用MySQL UPDATE的数据修订版。