12

事务管理

ACID 属性的实现

阅读量:2 · 预计 5 分钟读完

事务ACID状态转换并发执行
阅读进度8%

第12章:事务管理(Transaction Management)

一、导读

12.1 本章学习目标

事务是数据库操作的逻辑单位。本章讲解事务的概念、ACID 属性、并发执行的问题。

本章的核心学习目标包括:

  • 理解事务的概念:数据库操作的逻辑单位
  • 掌握 ACID 属性:原子性、一致性、隔离性、持久性
  • 理解并发执行的问题:丢失更新、脏读、不可重复读、幻读
  • 掌握可串行化:冲突可串行化、视图可串行化
  • 了解事务隔离级别

12.2 为什么事务管理如此重要

事务管理是数据库系统的核心功能。它保证:

  • 原子性:事务要么全部执行,要么全部不执行
  • 一致性:事务执行后数据库处于一致状态
  • 隔离性:并发事务互不干扰
  • 持久性:事务提交后数据永久保存

理解事务管理,对于开发可靠的数据库应用至关重要。


二、核心概念详解

2.1 事务的概念

事务(Transaction)是数据库操作的逻辑单位,由一组操作组成。

sql
BEGIN TRANSACTION;
    UPDATE Account SET balance = balance - 100 WHERE id = 'A';
    UPDATE Account SET balance = balance + 100 WHERE id = 'B';
COMMIT;

2.2 ACID 属性

2.2.1 原子性(Atomicity)

事务中的操作要么全部执行,要么全部不执行。

转账事务:
1. 从 A 账户扣除 100 元
2. 向 B 账户增加 100 元

如果步骤 1 成功但步骤 2 失败,必须回滚步骤 1。

实现机制:日志(Undo Log)

2.2.2 一致性(Consistency)

事务执行后,数据库从一个一致状态转换到另一个一致状态。

一致性约束:
- A 账户余额 + B 账户余额 = 总金额
- 转账前后总金额不变

实现机制:完整性约束、触发器、应用代码

2.2.3 隔离性(Isolation)

并发事务的执行效果与串行执行相同。

事务 T1:读取 A 的余额
事务 T2:修改 A 的余额

T1 和 T2 并发执行时,T1 应该看到 T2 修改前或修改后的值,
不应该看到中间状态。

实现机制:锁、多版本控制

2.2.4 持久性(Durability)

事务提交后,其对数据库的修改永久保存,即使系统崩溃也不会丢失。

实现机制:日志(Redo Log)、检查点

2.3 并发执行的问题

并发事务可能导致以下问题:

2.3.1 丢失更新(Lost Update)

两个事务同时修改同一数据,一个事务的修改被覆盖。

T1: 读取 A = 100
T2: 读取 A = 100
T1: A = A + 10, 写入 A = 110
T2: A = A + 20, 写入 A = 120  ← T1 的更新丢失

2.3.2 脏读(Dirty Read)

一个事务读取了另一个未提交事务的修改。

T1: 修改 A = 100
T2: 读取 A = 100  ← 脏读
T1: 回滚,A 恢复为 90
T2: 基于错误的 A = 100 继续执行

2.3.3 不可重复读(Non-Repeatable Read)

一个事务两次读取同一数据,结果不同。

T1: 读取 A = 100
T2: 修改 A = 200, 提交
T1: 再次读取 A = 200  ← 不可重复读

2.3.4 幻读(Phantom Read)

一个事务两次查询,第二次查询看到了第一次查询没有的行。

T1: 查询所有余额 > 100 的账户,得到 10 行
T2: 插入一个新账户,余额 150, 提交
T1: 再次查询所有余额 > 100 的账户,得到 11 行  ← 幻读

2.4 可串行化

可串行化是并发控制的正确性标准。

2.4.1 串行调度

事务一个接一个执行,没有交错。

调度 1:T1 → T2
调度 2:T2 → T1

2.4.2 冲突可串行化

如果调度可以通过交换相邻的非冲突操作转换为串行调度,则它是冲突可串行化的。

冲突操作:

  • 同一数据项的读写
  • 同一数据项的写读
  • 同一数据项的写写

2.4.3 优先图(Precedence Graph)

优先图用于判断调度是否冲突可串行化:

  • 节点:事务
  • 边:如果 Ti 的某个操作在 Tj 的某个操作之前,且它们冲突,则有一条从 Ti 到 Tj 的边

如果优先图无环,则调度冲突可串行化。

2.5 事务隔离级别

SQL 标准定义了四种隔离级别:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED不可能可能可能
REPEATABLE READ不可能不可能可能
SERIALIZABLE不可能不可能不可能
sql
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

三、重要知识点

3.1 事务状态转换

活跃 → 部分提交 → 提交
  ↓
失败 → 中止

3.2 并发控制的实现

  • 封锁:共享锁(S 锁)、排他锁(X 锁)
  • 两阶段封锁:扩展阶段、收缩阶段
  • 时间戳排序:每个事务有唯一的时间戳
  • 多版本并发控制(MVCC):维护数据的多个版本

3.3 死锁处理

  • 死锁预防:等待-死亡、受伤-等待
  • 死锁检测:等待图
  • 死锁恢复:回滚事务

四、常见误区与难点

4.1 误区一:"隔离级别越高越好"

隔离级别越高,并发度越低,性能越差。需要根据业务需求选择合适的隔离级别。

4.2 误区二:"事务越长越好"

长事务持有锁的时间长,降低并发度,增加死锁风险。

4.3 难点:理解可串行化

可串行化是抽象的概念,需要通过优先图来理解。

4.4 难点:理解 MVCC

MVCC 通过维护数据的多个版本实现并发控制,实现比较复杂。


五、实践应用

5.1 使用 MySQL 测试事务

sql
-- 开始事务
START TRANSACTION;

-- 执行操作
UPDATE Account SET balance = balance - 100 WHERE id = 'A';
UPDATE Account SET balance = balance + 100 WHERE id = 'B';

-- 提交事务
COMMIT;

-- 或者回滚
ROLLBACK;

5.2 设置隔离级别

sql
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

5.3 使用 JDBC 控制事务

java
Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false);  // 关闭自动提交

try {
    Statement stmt = conn.createStatement();
    stmt.executeUpdate("UPDATE Account SET balance = balance - 100 WHERE id = 'A'");
    stmt.executeUpdate("UPDATE Account SET balance = balance + 100 WHERE id = 'B'");
    conn.commit();  // 提交事务
} catch (SQLException e) {
    conn.rollback();  // 回滚事务
}

六、本章小结

第12章讲解了事务管理:

事务 是数据库操作的逻辑单位

ACID 属性 保证事务的可靠性

并发执行 可能导致丢失更新、脏读、不可重复读、幻读

可串行化 是并发控制的正确性标准

隔离级别 提供不同级别的并发控制

后续章节将讲解并发控制的具体实现。