una - PHP/MySQL: cómo evitar dos solicitudes*Actualización
mostrar tablas de una base de datos mysql en php (5)
Así es como solía hacerlo hace muchos años ..
results = query("UPDATE table SET value=value-5 WHERE value>=5 AND ID=1")
if (results == 1) YEY!
(¿Sigue siendo un método confiable?)
Tengo alguna pregunta ... ejemplo: un usuario comprará algo por su USD
- Verifique su saldo en USD
- Deducir el USD de su cuenta
- Hacer un pedido -> cola de pedidos
- el usuario obtiene su artículo y el otro obtiene su USD
Digamos que los usuarios hacen 5 solicitudes en el mismo segundo (muy rápido). Entonces es posible (y sucede) que se ejecuten 5 solicitudes. Solo tiene dinero para comprar solo a partir de 1 pedido. Ahora las solicitudes son tan rápidas, que el guión revisa su saldo, pero no es tan rápido, que deduce el dinero de su cuenta. ¡Entonces las solicitudes pasarán dos veces! ¿Cómo resolverlo?
Utilizo LOCK en mysql antes de comenzar el proceso:
- IS_FREE_LOCK - verificar si hay un bloqueo para este usuario si no es así -> 2.
- GET_LOCK: establece el bloqueo
- hacer la orden / transacción
- RELEASE_LOCK: libera el bloqueo
Pero esto realmente no funciona. ¿Hay otra manera?
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));
}
Actualización Probó la función de transacción con SELECCIONAR ... PARA ACTUALIZAR
$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();
En primer lugar, debe usar transacciones, pero eso no es suficiente. En su transacción, puede usar SELECT FOR UPDATE
.
Básicamente dice: "Voy a actualizar los registros que estoy seleccionando" , por lo que está configurando los mismos bloqueos que establecería una UPDATE
. Pero recuerde que esto tiene que suceder dentro de una transacción con autocommit desactivado.
Usa TRANSACTION y si falla puedes retroceder.
Por ejemplo, supongamos que el saldo actual es de $ 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
necesita usar TRANSACTION en el nivel de aislamiento SERIALIZABLE.
Necesita utilizar la revisión de datos para MySQL UPDATE.