我想获得最后的余额并从后端更新xxx用户的一些交易。 不幸的是,同时,xxx也从前端执行事务,所以当我处理我的查询时,xxx也处理相同的查询,所以它得到相同的最后余额。
这是我的剧本。
假设:xxx上次余额为10000
$transaction = 1000;
$getData = mysqli_fetch_array(mysqli_query($conn,"select balance from tableA where user='xxx'"));
$balance = $getData["balance"] - $transaction; //10000 - 1000 = 9000
mysqli_query($conn,"update tableA set balance='".$balance."' where user='xxx'");
同时用户xxx从前端做交易..
$transaction = 500;
$getData = mysqli_fetch_array(mysqli_query($conn,"select balance from tableA where user='xxx'"));
$balance = $getData["balance"] - $transaction; //10000-500 it should be 9000-500
mysqli_query($conn,"update tableA set balance='".$balance."' where user='xxx'");
如何首先完成查询,然后用户xxx可以处理查询?
答案 0 :(得分:3)
您可以使用TableA
LOCK TABLES 命令锁定表格“MySQL
”。
这是逻辑流程:
LOCK TABLES "TableA"
WRITE;
执行您的第一个查询
然后
请参阅:
答案 1 :(得分:2)
使用InoBD引擎和事务使其成为ACID(https://en.wikipedia.org/wiki/ACID)
mysqli_begin_transaction($conn);
...
mysqli_commit($conn)
另外,为什么不使用查询来增加余额
mysqli_query($conn,"update tableA set balance= balance + '".$transaction."' where user='xxx'");
答案 2 :(得分:2)
这是可用的方法之一。
你必须为你的桌子使用InnoDB
引擎。 InnoDB支持行锁,因此您不需要锁定整个表来仅更新一个与给定用户相关的ROW。
(表锁定将阻止执行其他INSERT/UPDATE/DELETE
操作,导致他们必须等待释放此表LOCK。
在InnoDB中,当您使用ROW LOCK
执行SELECT
查询时,您可以实现FOR UPDATE
。
(但在此你必须使用事务来实现LOCK
)。当你SELECT ... FOR UPDATE
时
在事务中,mysql会锁定您选择的给定行,直到提交事务为止。
并且假设您在后端为用户输入XXX进行SELECT ... FOR UPDATE
查询,同时前端对同一个XXX进行相同的查询。
执行的第一个查询(来自后端)将锁定数据库中的条目,第二个查询将等待第一个查询完成,
这可能会导致前端请求完成延迟。
但是要使这种情况起作用,您必须同时放置前端和后端查询
在交易中,两个SELECT
查询最终都必须FOR UPDATE
。
所以你的代码看起来像这样:
$transaction = 1000;
mysqli_begin_transaction($conn);
$getData = mysqli_fetch_array(mysqli_query($conn,"SELECT balance FROM tableA WHERE user='xxx' FOR UPDATE"));
$balance = $getData["balance"] - $transaction; //10000 - 1000 = 9000
mysqli_query($conn,"UPDATE tableA SET balance='".$balance."' WHERE user='xxx'");
mysqli_commit($conn);
如果这是你的后端代码,前端代码看起来应该非常相似 - 开始/提交事务+ FOR UPDATE
。
关于FOR UPDATE
的最好的事情之一是,如果您需要查询LOCK
某行并使用此数据进行一些计算
在给定方案中,但同时您需要选择同一行的其他查询,并且否需要该行中的最新数据,
你可以简单地完成这个没有事务的查询,最后没有FOR UPDATE
。因此,您将有LOCKED
行和正在阅读的其他正常SELECTs
(当然,他们会读取LOCK
开始之前存储的旧信息。
答案 3 :(得分:0)
基本上有两种方法可以解决这个问题:
在这种情况下最常见的是使用事务,以确保您执行的所有操作都是原子操作。这意味着如果一个步骤失败,则所有内容都会在更改开始之前回滚。
通常情况下,人们也会在查询中执行操作,这很简单。因为数据库引擎能够进行简单的计算。在这种情况下,您可能需要检查用户是否确实对其帐户有足够的信用,这反过来表明您需要检查。
在您减去金额之后,我只是将支票移动到保险柜上。 (防止比赛条件等)
一个让您入门的简单示例:
$conn = new mysqli();
/**
* Updates the user's credit with the amount specified.
*
* Returns false if the resulting amount is less than 0.
* Exceptions are thrown in case of SQL errors.
*
* @param mysqli $conn
* @param int $userID
* @param int $amount
* @throws Exception
* @return boolean
*/
function update_credit (mysqli $conn, $userID, $amount) {
// Using transaction so that we can roll back in case of errors.
$conn->query('BEGIN');
// Update the balance of the user with the amount specified.
$stmt = $conn->prepare('UPDATE `table` SET `balance` = `balance` + ? WHERE `user` = ?');
$stmt->bind_param ('dd', $amount, $userID);
// If query fails, then roll back and return/throw an error condition.
if (!$stmt->execute ()) {
$conn->query ('ROLLBACK');
throw new Exception ('Count not perform query!');
}
// We need the updated balance to check if the user has a positive credit counter now.
$stmt = $conn->prepare ('SELECT `balance` FROM `table` WHERE `user` = ?');
$stmt->bind_param ('d', $userID);
// Same as last time.
if (!$stmt->execute ()) {
$conn->query ('ROLLBACK');
throw new Exception ('Count not perform query!');
}
$stmt->bind_result($amount);
$stmt->fetch();
// We need to inform the user if he doesn't have enough credits.
if ($amount < 0) {
$conn->query ('ROLLBACK');
return false;
}
// Everything is good at this point.
$conn->query ('COMMIT');
return true;
}
答案 4 :(得分:0)
也许你的问题只是存储余额的方法。你为什么把它放在一个领域?你失去了做这件事的所有历史。
创建一个表:transactions_history。然后对于每个事务,执行INSERT查询,传递用户,事务值和操作(存入或取消)。
然后,要向您的用户显示他当前的余额,只需对其所有交易历史记录执行SELECT,正确执行操作,最后他将看到实际的正确余额。而且你也可以防止错误同时进行2次UPDATE查询(虽然&#34;同一时间&#34;它并不像我们想象的那么常见)。
答案 5 :(得分:-2)
你可以使用这样的交易。 $ balance是你要减去的余额。如果查询执行得好,它将显示更新的余额,否则它将回滚到初始位置,异常错误将显示失败错误。
try {
$db->beginTransaction();
$db->query('update tableA set balance=balance-'".$balance."' where user='xxx'" ');
$db->commit();
} catch (Exception $e) {
$db->rollback();
}