ti-enxame.com

PHP / MySQL - fazendo duas coisas

Eu tenho alguma pergunta ... exemplo: um usuário vai comprar algo para seu USD

  1. Verifique seu saldo em USD
  2. Deduzir o USD da sua conta
  3. Faça um pedido -> fila de pedidos
  4. usuário recebe seu item e o outro recebe seu USD

Digamos, os usuários fazem 5 pedidos no mesmo segundo (muito rápido). Então, é possível (e acontecer) que 5 pedidos estão em execução. Ele só tem dinheiro para comprar apenas a partir de 1 pedido. Agora os pedidos São tão rápidos, que o script verifica seu saldo, mas não é tão rápido, que deduz o dinheiro De sua conta. Então os pedidos vão passar duas vezes! Como resolver isso?

Eu uso o LOCK no mysql antes de iniciar o processo:

  1. IS_FREE_LOCK - verifique se há um bloqueio para este usuário, se não -> 2.
  2. GET_LOCK - define o bloqueio
  3. faça o pedido/transação
  4. RELEASE_LOCK - libera o bloqueio

Mas isso realmente não funciona. Existe outro caminho?

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));
}

Update Testado a função de transação com 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();
22
DjangoSi

Primeiro, você tem que usar transações, mas isso não é suficiente. Na sua transação, você pode usar SELECT FOR UPDATE .

É basicamente dizendo, "Eu vou atualizar os registros que estou selecionando" , por isso está definindo os mesmos bloqueios que um UPDATE iria definir. Mas lembre-se que isso tem que acontecer dentro de uma transação com o autocommit desativado.

25
user1703809

Use TRANSACTION e, se falhar, você poderá reverter.

Por exemplo, suponha que o saldo atual seja de US $ 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
6
Oden

Isto é como eu costumava fazer isso há muitos anos ..

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

(Isso ainda é um método confiável?)

5
Kols

você precisa usar TRANSACTION no nível de isolamento SERIALIZABLE.

1
Smoky McPot

Você precisa usar a revisão de dados para o MySQL UPDATE.

0
Ego Slayer