PostgreSQL 事务 - 保证交易的完整性
本文介绍了在 PostgreSQL 中如何开启、提交和回滚事务。
数据库事务是一种保证交易的完整性的机制。事务是一个不可分隔的工作单元,一个事务之内的所有的操作要么全部执行,要么全部不执行。
比如,在一个银行系统中,用户 A 向用户 B 转账 500 元。这个交易主要包含两个操作:
- 从用户 A 的账户余额扣除 500 元。
- 将用户 B 的账户余额增加 500 元。
上面的两个操作要么全部执行,要么全部不执行,否则会带来错误。
数据库事务的特性
数据库事务具有四大特性:
- 原子性(Atomicity): 事务中的操作要么全部执行,要么全部不执行。
- 一致性(Consistency): 一个事务必须是数据库从一个一致状态变成另一个一致状态。
- 隔离性(Isolation): 多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability): 已提交的事务将永久存储在数据库中。
数据库事务的四大特性通常被简称为 ACID。
PostgreSQL 事务命令
PostgreSQL 提供了一些 SQL 命令用来控制事务,包括:开启一个事务、提交一个事务、回滚一个事务。
开启一个事务
要开启一个 PostgreSQL 数据库事务,您可以使用以下命令中的一个:
START TRANSACTION;
BEGIN TRANSACTION;
BEGIN WORK;
BEGIN;
开启一个事务后,后续的所有操作都属于该事务,直到事务被提交或者被回滚。
提交一个事务
要提交该事务内的所有操作,您可以使用以下命令中的一个:
COMMIT TRANSACTION;
COMMIT WORK;
COMMIT;
提交事务将当前事务内的所有操作写入数据库,并结束当前事务。
回滚一个事务
要回滚该事务内的所有操作,您可以使用以下命令中的一个:
ROLLBACK TRANSACTION;
ROLLBACK WORK;
ROLLBACK;
回滚事务将撤销当前事务内的所有的操作,并结束当前事务。
PostgreSQL 事务实例
我们将通过一个银行账户余额的表来演示事务。下面的语句创建了表 user_balance
:
CREATE TABLE user_balance (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
balance DEC(15,2) NOT NULL
);
事务初体验
让我们在一个事务中插入一个数据,以演示事务的用法。
-
(会话 1)使用下面的语句开启一个事务:
BEGIN;
-
(会话 1)使用下面的语句插入一行数据:
INSERT INTO user_balance (id, name, balance) VALUES (1, 'Tom', '2500');
-
(会话 1)使用下面的语句查看
user_balance
表中数据:SELECT * FROM user_balance;
id | name | balance ----+------+--------- 1 | Tom | 2500.00 (1 row)
-
(会话 2)重新打开一个会话并登录到刚刚的数据库中,使用下面的语句查看
user_balance
表中数据:SELECT * FROM user_balance;
id | name | balance ----+------+--------- (0 rows)
这里,您并不能看到刚刚插入的行。这是因为在会话 1 中的插入操作在一个事务中,并且事务还未提交,因此事务之外并不能看到还未提交的数据。
-
(会话 1)回到原来的会话,使用下面的语句提交事务:
COMMIT;
-
(会话 2)回到会话 2,使用下面的语句查看
user_balance
表中数据:SELECT * FROM user_balance;
id | name | balance ----+------+--------- 1 | Tom | 2500.00 (1 row)
此时,由于会话 1 中的事务已经提交,您可以在会话 2 中看到刚刚插入的行了。
银行转账:事务中的多个操作
使用下面的语句插入 2 行数据用于这个示例:
INSERT INTO user_balance (id, name, balance)
VALUES (2, 'Tim', '1500'), (3, 'Jim', '1000');
使用下面的语句看一下 user_balance
表中行:
SELECT * FROM user_balance;
id | name | balance
----+------+---------
1 | Tom | 2500.00
2 | Tim | 1500.00
3 | Jim | 1000.00
(3 rows)
假设, Tom 需要向 Tim 转账 500 元。您需要完成如下两个操作:
- 将 Tom 的余额扣减 500 元
- 将 Tim 的余额增加 500 元
这里有两个操作,为了保证转账交易的完整性,因此我们需要使用事务。
-
使用下面的语句开启一个事务:
BEGIN;
-
使用下面的语句将 Tom 的余额减少 500 元:
UPDATE user_balance SET balance = balance - 500 WHERE id = 1;
-
使用下面的语句将 Tim 的余额增加 500 元:
UPDATE user_balance SET balance = balance + 500 WHERE id = 2;
-
使用下面的语句查看
user_balance
表中数据,已验证数据的是否正确:SELECT * FROM user_balance WHERE id IN (1, 2);
id | name | balance ----+------+--------- 1 | Tom | 2000.00 2 | Tim | 2000.00 (2 rows)
这里,Tom 和 Tim 转账后的余额都是正确的。
-
使用下面的语句提交事务:
COMMIT;
回滚事务
如果在提交事务之前出现了任何异常,您都可以通过回滚事务来撤销之前的操作。
比如,您不小心将钱转账给了 id 为 3 的用户,只要您在提交之前发现了问题, 您可以通过 ROLLBACK
命令回滚当前事务。
首先,看一下当前 user_balance
表中的行:
SELECT * FROM user_balance;
id | name | balance
----+------+---------
3 | Jim | 1000.00
1 | Tom | 2000.00
2 | Tim | 2000.00
(3 rows)
下面的语句假设您不小心将钱转账给了 id 为 3 的用户:
BEGIN;
UPDATE user_balance
SET balance = balance - 500
WHERE id = 1;
UPDATE user_balance
SET balance = balance + 500
WHERE id = 3;
在提交之前您使用以下语句检查操作是否正确:
SELECT *
FROM user_balance
WHERE id IN (1, 2);
id | name | balance
----+------+---------
2 | Tim | 2000.00
1 | Tom | 1500.00
(2 rows)
您发现 Tom 的余额少了 500, 而 Tim 的余额没有变化。这是一个错误。因此您需要使用 ROLLBACK
来回滚事务。
事务被回滚后,数据恢复到操作之前的状态,您就可以继续完成转账了。
结论
在 PostgreSQL 中,事务可以保证交易的完整性。 BEGIN
语句用于开启一个事务,COMMIT
语句用户提交一个事务 和 ROLLBACK
语句用户回滚一个事务。