繁体   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