[英]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:但是,我想以原子方式完成所有这些工作,原因有两个:
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.