22

いくつか質問があります...例: ユーザーは米ドルで何かを購入します

  1. 彼の USD 残高を確認する
  2. 彼の口座から米ドルを差し引く
  3. 注文する -> 注文キュー
  4. ユーザーが自分のアイテムを取得し、他のユーザーが米ドルを取得します

たとえば、ユーザーが 1 秒間に 5 つのリクエストを作成したとします (非常に高速です)。そのため、5 つのリクエストが実行されている可能性があります (実際に起こります)。彼は 1 つの要求からのみ購入するお金しかありません。現在、要求は非常に高速であるため、スクリプトは彼の残高をチェックしますが、彼の口座からお金を差し引くほど高速ではありません。したがって、リクエストは 2 回渡されます。それを解決する方法は?

プロセスを開始する前に、mysql で LOCK を使用します。

  1. IS_FREE_LOCK - このユーザーにロックがあるかどうかを確認します (そうでない場合) -> 2.
  2. GET_LOCK - ロックを設定します
  3. 注文/取引を行う
  4. RELEASE_LOCK - ロックを解放します

しかし、これは実際には機能しません。別の方法はありますか?

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();
4

5 に答える 5

27

まず、トランザクションを使用する必要がありますが、それだけでは十分ではありません。取引では、 を使用できますSELECT FOR UPDATE

基本的には、「選択しているレコードを更新する」と言っているので、設定するのと同じロックを設定していUPDATEます。ただし、これは、自動コミットがオフになっているトランザクション内で発生する必要があることに注意してください。

于 2013-02-22T15:04:28.800 に答える
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
于 2013-02-22T14:53:16.630 に答える
0

MySQL UPDATE にはデータ リビジョンを使用する必要があります。

于 2016-02-09T10:43:01.210 に答える