[英]SQL prevent select same field at same time
我想获取最后一笔余额,并从后端更新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可以处理查询?
您可以使用MySQL
LOCK TABLES命令锁定表“ TableA
”。
这是逻辑流程:
锁定表"TableA"
写;
执行您的第一个查询
然后
看到:
使用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'");
这是可用的方法之一。
您必须为表使用InnoDB
引擎。 InnoDB支持行锁,因此您无需锁定整个表即可仅更新与给定用户相关的一个ROW。 (表锁定将阻止其他INSERT/UPDATE/DELETE
操作被执行,从而导致它们必须等待该表LOCK释放)。
在InnoDB中,使用FOR UPDATE
执行SELECT
查询时可以实现ROW LOCK
。 (但是在这种情况下,您必须使用transaction来实现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);
如果这是您的后端代码,那么前端代码应该看起来非常相似-具有begin / commit transaction + FOR UPDATE
。
一对最好的事情 FOR UPDATE
是,如果你需要一个查询来LOCK
一些行和一些计算这个数据在给定的情况下,但在同一时间,你需要的是在选择同一行的其他查询,他们这样做没有必要该行中的最新数据,那么您可以简单地执行此查询,而无需进行事务处理并且最终也不会进行FOR UPDATE
。 因此,您将有LOCKED
行和其他普通的SELECTs
从中读取(当然,它们将读取LOCK
启动之前存储的旧信息...)。
基本上有两种方法可以解决此问题:
在这种情况下,最常见的一种是使用事务,以确保您执行的所有操作都是原子操作。 这意味着,如果某个步骤失败,则所有内容都会回滚到更改开始之前。
通常情况下,查询操作本身也可以进行,就像这样简单。 由于数据库引擎不仅仅具有执行简单计算的能力。 在这种情况下,您可能需要检查用户的帐户上是否确实有足够的信用额度,这又表明您需要检查。
为了安全起见,我会在减去金额后将支票移至。 (保护赛车条件等)
让您快速入门的快速示例:
$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;
}
也许您的问题只是存储余额的方式。 你为什么把它放在田里? 您会丢失所有的交易历史记录。
创建一个表:transactions_history。 然后针对每个事务,执行INSERT查询,传递用户,事务值和操作(存款或提款)。
然后,要向您的用户显示他的当前余额,只需对他所有的交易历史记录进行SELECT,然后正确执行操作,最后他将看到实际的正确余额。 而且您还可以防止错误同时执行2个UPDATE查询(尽管“相同的时间”并不像我们想象的那么普遍)。
您可以使用这样的交易。 $ balance是您要减去的余额。如果查询执行得好,它将显示更新后的余额,否则将回滚到初始位置并且异常错误将显示失败错误。
try {
$db->beginTransaction();
$db->query('update tableA set balance=balance-'".$balance."' where user='xxx'" ');
$db->commit();
} catch (Exception $e) {
$db->rollback();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.