簡體   English   中英

如何有條件地更新 SQL 表?

[英]How to conditional update SQL tables?

我有兩個這樣的數據庫表:

CREATE TABLE IF NOT EXISTS accounts (
  id        INT PRIMARY KEY NOT NULL,
  balance   INT NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS transactions (
  from      INT FOREIGN KEY REFERENCES accounts(id),
  to        INT FOREIGN KEY REFERENCES accounts(id),
  amount    INT NOT NULL
)

在我的應用程序邏輯中,每當有新的交易請求進來時,我想首先檢查 from 賬戶是否有足夠的余額,然后我想從 from 賬戶中減去金額,將金額添加到 to 賬戶,然后添加一個新行到事務表。

但是,我想以原子方式完成所有這些工作,原因有兩個:

  • 防止競爭條件。 如果有兩個請求將 5 美元從賬戶 1 轉移到賬戶 2,而賬戶 1 總共只有 5 美元,我想防止余額發生雙重支出。 因此,必須以原子方式檢查足夠的余額和隨后的余額更新。
  • 如果應用程序崩潰,防止不一致的 state。

是否可以在單個 SQL 語句中執行此操作? 假設我們正在使用 Postgres。

例如,我知道我可以執行UPDATE accounts SET balance = balance - 6 WHERE id = 1 and balance > 6; 檢查帳戶是否有足夠的余額並同時更新余額。

我還聽說過一些名為select... for update ,但我不確定這是否可以幫助我。

另外,查詢的結果應該表明交易是否成功,如果沒有,我可以向客戶端顯示一些錯誤消息嗎?

解決方案不是將所有內容都塞進一個 SQL 語句中。 除了復雜之外,它不一定會保護您免受競爭條件的影響。

使用數據庫事務。 為避免異常,請使用SELECT... FOR UPDATE以防止並發修改,或者使用REPEATABLE READ隔離級別並在遇到序列化錯誤時重復事務。

為了保護自己免受死鎖,請確保始終先鎖定具有較低id的帳戶。

這是一個偽代碼示例:

EXEC SQL START TRANSACTION;

if (id1 < id2) {
    EXEC SQL SELECT balance INTO :bal1 FROM accounts
         WHERE id = :id1 FOR UPDATE;
    EXEC SQL SELECT balance INTO :bal2 FROM accounts
         WHERE id = :id2 FOR UPDATE;
} else {
    EXEC SQL SELECT balance INTO :bal2 FROM accounts
         WHERE id = :id2 FOR UPDATE;
    EXEC SQL SELECT balance INTO :bal1 FROM accounts
         WHERE id = :id1 FOR UPDATE;
}

if (bal1 < amount) {
    EXEC SQL ROLLBACK;
    throw_error('too little money on account');
}

EXEC SQL UPDATE accounts SET balance = :bal1 - :amount WHERE id = :id1;
EXEC SQL UPDATE accounts SET balance = :bal1 + :amount WHERE id = :id2;
EXEC SQL INSERT INTO transaction ("from", "to", amount)
    VALUES (:id1, :id2, :amount);

EXEC SQL COMMIT;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM