簡體   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