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 语句用户回滚一个事务。