[英]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.