PostgreSQL 数据库事务
事务(Transactions)是数据库管理的基础,用于确保数据的一致性和可靠性。
本章将深入探讨事务的核心概念,重点学习如何在 PostgreSQL 中使用 BEGIN 和 COMMIT 命令来开启和结束事务。
1. 回顾事务的 ACID 特性
作为上一模块的简要回顾,事务保证了数据库操作以可靠和可预测的方式进行,并严格遵循 ACID 特性:
- 原子性 (Atomicity):事务是一个不可分割的工作单元;其中的所有操作要么全部成功,要么全部失败。
- 一致性 (Consistency):事务将数据库从一个有效的状态转换到另一个有效的状态。它通过遵守预定义的规则和约束来维护数据完整性。
- 隔离性 (Isolation):事务之间是相互隔离的,这可以防止相互干扰,并确保并发(同时进行)的事务不会破坏数据。
- 持久性 (Durability):一旦事务被提交,它所做的更改就是永久性的,即使发生系统故障也能保存下来。
2. 使用 BEGIN 命令开启事务
在 PostgreSQL 中,BEGIN 命令用于初始化(开启)一个事务。它标志着一系列应当被视为单个逻辑工作单元的操作的开始。在发出 COMMIT(提交)或 ROLLBACK(回滚)命令之前,对数据库所做的所有更改都属于这个事务的一部分,并且不会被永久保存。
2.1 语法
基本语法非常直观:
BEGIN;可选地,你可以在事务开始时指定隔离级别 (isolation level) 和访问模式 (access mode)。我们将在后面的章节中详细讨论隔离级别,但现在你只需要知道,它决定了并发事务之间相互隔离的程度。访问模式则定义了该事务是只读的(read-only)还是读写的(read-write)。
BEGIN ISOLATION LEVEL level READ WRITE; -- 或者 READ ONLY (只读)这里的 level(级别)可以是 SERIALIZABLE(可串行化)、REPEATABLE READ(可重复读)或 READ COMMITTED(读已提交)。如果没有指定 ISOLATION LEVEL 和/或 READ WRITE,数据库将使用默认的隔离级别和读写模式。
2.2 示例
让我们考虑一个简化的银行业务场景。我们有一个名为 accounts 的表,包含 account_id(账户 ID)和 balance(余额)两列。我们想要从账户 1 转账 100 美元到账户 2。
-- 开启一个新事务
BEGIN;
-- 从账户 1 扣除 100 美元
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 向账户 2 增加 100 美元
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 提交事务以保存更改
COMMIT;在这个例子中,BEGIN 命令开启了事务。随后的 UPDATE 语句修改了 accounts 表。然而,这些更改此时还不是永久的。只有当执行了 COMMIT 命令时,它们才会被永久保存。例如,如果账户 1 的余额不足,我们将使用 ROLLBACK 命令(将在下一章讨论)来撤销这些更改。
2.3 隐式事务
在某些 PostgreSQL 客户端工具和配置中,事务可能会被隐式(自动)开启。例如,PostgreSQL 的交互式终端 psql 通常默认运行在“自动提交 (autocommit)”模式下。在自动提交模式下,每条单独的 SQL 语句都会被视为一个独立的事务,并在成功执行后自动提交。
你可以使用命令 SET AUTOCOMMIT OFF; 来禁用自动提交。当自动提交被关闭时,你必须显式地使用 BEGIN 和 COMMIT(或 ROLLBACK)来管理事务。
3. 使用 COMMIT 命令提交事务
COMMIT 命令用于最终完成一个事务,使数据库中的所有更改成为永久性的。一旦事务被提交,这些更改就保证具备了持久性(Durability),这意味着它们能够在系统崩溃或其他故障中存活下来。
3.1 语法
提交事务的语法同样很简单:
COMMIT;3.2 示例
继续使用刚才的银行业务场景,在执行完转账操作后,我们使用 COMMIT 来保存更改:
-- 开启一个新事务
BEGIN;
-- 从账户 1 扣除 100 美元
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 向账户 2 增加 100 美元
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 提交事务以保存更改
COMMIT;如果 COMMIT 命令执行成功,这两个 UPDATE 操作就会被永久应用到 accounts 表中。
3.3 COMMIT 的重要性
如果没有 COMMIT 命令,在事务内部所做的任何更改都只对当前会话可见,并且不会被永久保存。这种机制确保了如果事务的任何部分失败,整个事务都可以被回滚,从而维护了数据的一致性。
4. 实战案例:电商订单处理
设想一个电商应用程序,客户在其中下达了一个订单。以下操作需要在单个事务中完成:
- 更新
products(商品)表,减少订购商品的库存数量。 - 在
orders(订单)表中创建一条包含订单详情的新记录。 - 在
order_items(订单明细)表中为订单中的每一件商品创建记录。 - 更新
customer_accounts(客户账户)表以反映支付扣款。
BEGIN;
-- 更新商品库存数量
UPDATE products SET quantity = quantity - 2 WHERE product_id = 101;
UPDATE products SET quantity = quantity - 1 WHERE product_id = 102;
-- 插入订单详情
INSERT INTO orders (customer_id, order_date, total_amount) VALUES (123, NOW(), 199.98);
-- 获取刚生成的订单的订单 ID
-- 注意:此示例为了演示使用了简化方法。在真实场景中,你会使用特定数据库的函数(例如 PostgreSQL 中的 RETURNING 子句)来获取新生成的订单 ID
SELECT currval(pg_get_serial_sequence('orders', 'order_id')) INTO order_id;
-- 插入订单明细
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (order_id, 101, 2, 79.99);
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (order_id, 102, 1, 39.99);
-- 更新客户账户余额
UPDATE customer_accounts SET balance = balance - 199.98 WHERE customer_id = 123;
COMMIT;如果这些操作中有任何一个失败(例如,库存不足、支付失败),则应该使用 ROLLBACK 回滚整个事务,以防止数据出现不一致。
5. 假设场景:大学选课系统
想象一个大学的选课系统,学生在其中注册课程。一个选课事务将包含以下步骤:
- 检查学生是否满足该课程的先修条件。
- 验证该课程是否还有剩余座位。
- 更新学生的记录,将该课程添加进去。
- 更新课程的记录,反映新的报名人数。
BEGIN;
-- 检查先修条件 (简化版)
SELECT has_prerequisites FROM students WHERE student_id = 'S123';
-- 检查剩余座位 (简化版)
SELECT seats_available FROM courses WHERE course_id = 'CS101';
-- 更新学生记录
INSERT INTO student_courses (student_id, course_id) VALUES ('S123', 'CS101');
-- 更新课程记录
UPDATE courses SET seats_available = seats_available - 1 WHERE course_id = 'CS101';
COMMIT;如果这些检查中的任何一个失败(例如,学生不满足先修条件,或者课程已满),事务就应该被回滚,以维护选课数据的完整性。