繁体   English   中英

SQL阻止同时选择相同的字段

[英]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"写;

  • 执行您的第一个查询

然后

  • 解锁表;

看到:

http://dev.mysql.com/doc/refman/5.5/zh-CN/lock-tables.html

使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM