简体   繁体   English

如何有条件地更新 SQL 表?

[英]How to conditional update SQL tables?

I have two database tables like so:我有两个这样的数据库表:

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
)

In my application logic, whenever a new transaction request comes in, I want to check first if the from account has sufficient balance, then I want to subtract the amount from the from account, add amount to the to account, and add a new row to the transactions table.在我的应用程序逻辑中,每当有新的交易请求进来时,我想首先检查 from 账户是否有足够的余额,然后我想从 from 账户中减去金额,将金额添加到 to 账户,然后添加一个新行到事务表。

However, I want to do all of this atomically, for two reasons:但是,我想以原子方式完成所有这些工作,原因有两个:

  • Prevent race conditions.防止竞争条件。 If two requests come in to transfer $5 from account 1 to account 2, and account 1 only has $5 total, I want to prevent double spending of the balance.如果有两个请求将 5 美元从账户 1 转移到账户 2,而账户 1 总共只有 5 美元,我想防止余额发生双重支出。 Thus, the checking of sufficient balance and subsequent updating of balances has to happen atomically.因此,必须以原子方式检查足够的余额和随后的余额更新。
  • Prevent inconsistent state if application crashes.如果应用程序崩溃,防止不一致的 state。

Is it possible to do this in a single SQL statement?是否可以在单个 SQL 语句中执行此操作? assume we are using Postgres.假设我们正在使用 Postgres。

I know for example I can do UPDATE accounts SET balance = balance - 6 WHERE id = 1 and balance > 6;例如,我知道我可以执行UPDATE accounts SET balance = balance - 6 WHERE id = 1 and balance > 6; to check if account has sufficient balance and simultaneously update the balance at the same time.检查帐户是否有足够的余额并同时更新余额。

I've also heard of something called select... for update but I'm not sure if this can help me here.我还听说过一些名为select... for update ,但我不确定这是否可以帮助我。

Also, the result of the query should indicate whether the transaction was successful, so I can display some error message to client if it is not?另外,查询的结果应该表明交易是否成功,如果没有,我可以向客户端显示一些错误消息吗?

The solution is not to cram everything into a single SQL statement.解决方案不是将所有内容都塞进一个 SQL 语句中。 Apart from being complicated, it will not necessarily protect you from race conditions.除了复杂之外,它不一定会保护您免受竞争条件的影响。

Use database transactions.使用数据库事务。 To avoid anomalies, either lock everything you read with SELECT... FOR UPDATE against concurrent modifications, or use the REPEATABLE READ Isolation level and repeat the transaction if you get a serialization error.为避免异常,请使用SELECT... FOR UPDATE以防止并发修改,或者使用REPEATABLE READ隔离级别并在遇到序列化错误时重复事务。

To protect yourself from deadlocks, make sure that you always lock the account with the lower id first.为了保护自己免受死锁,请确保始终先锁定具有较低id的帐户。

Here is a pseudo-code example:这是一个伪代码示例:

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