数据库系统原理 · 事务管理与恢复 · 自学总结

三大核心问题:数据库如何保证操作逻辑完整?并发执行如何正确?故障后数据如何恢复? 所有答案围绕 ACID 展开。


一、事务(Transaction)

1.1 是什么?

事务 = 具有完整逻辑意义的数据库操作序列,是数据库执行的最小工作单位。

一个事务包含一条或多条 SQL(如查询、插入、更新、删除),这些操作要么全部成功,要么全部失败,不能拆半

例子:飞机订票 =「更新售票点已售票数」+「更新航班余票数」,两个操作必须打包成一个事务。

1.2 为什么要有事务?

现实中一个业务往往需要多条 SQL 配合。如果只执行一半就停了,数据库会处于逻辑混乱的"半成品"状态。

没有事务的后果 例子
数据不一致 转账只扣 A 不增 B,钱凭空消失
无法回滚 操作到一半出错,已经改的数据赖在数据库里
无法保证业务规则 已售票数 + 余票数 ≠ 总座位数

1.3 事务怎么用?

基本语法(SQL)

复制代码
 BEGIN TRANSACTION;   -- 开启事务
 UPDATE 账户表 SET 余额 = 余额 - 1000 WHERE 账户 = 'A';  -- 操作1
 UPDATE 账户表 SET 余额 = 余额 + 1000 WHERE 账户 = 'B';  -- 操作2
 COMMIT;                -- 全部成功,提交生效
 -- 或
 ROLLBACK;              -- 任何一步出错,全部撤销

单条 SQL 默认也是事务------隐式自动提交,只是你看不到 BEGIN/COMMIT。


二、事务的 ACID 特性

2.1 ACID 是什么?

特性 英文 含义
A Atomicity(原子性) 要么全做,要么全不做
C Consistency(一致性) 事务执行前后,数据库都处于合法状态(满足所有约束)
I Isolation(隔离性) 多个事务并发执行时互不干扰
D Durability(持久性) 事务提交后,修改永久生效,故障不丢失

2.2 为什么要有 ACID?

四根支柱撑起数据库可靠性:

少了哪个 会出现什么问题
没有原子性 操作做一半停了,数据成"半成品"
没有一致性 转账后总金额变了,违反业务规则
没有隔离性 多人同时订票,同一张票卖给两个人
没有持久性 提交后断电,数据没了,用户白操作

2.3 由谁保证?怎么用?

特性 由谁保证 怎么用
原子性 恢复管理模块(UNDO 操作) 事务失败时自动回滚所有已做更新
一致性 应用开发人员 + DBMS 完整性约束 写正确的业务逻辑,DBMS 用 CHECK/FOREIGN KEY 辅助校验
隔离性 并发控制模块(封锁协议) 通过加锁机制控制并发事务的执行顺序
持久性 恢复管理模块(REDO 操作 + 日志) 提交先写日志,故障后通过日志重做

三、并发控制

3.1 是什么?

并发控制 = 管理多个事务同时执行,保证结果正确

DBMS 允许多个事务"穿插"执行(宏观并行、微观串行),但需要用机制防止它们互相干扰。

3.2 为什么要并发?

不并发的问题 并发的好处
事务排队执行,后面的苦等 提高吞吐量,减少平均响应时间
CPU 等磁盘时闲着浪费 事务 A 等 I/O 时,事务 B 用 CPU 计算
用户体验差 多人同时操作时系统不卡顿

3.3 并发带来的三类问题

问题 是什么 例子 危害
读脏数据 读到别人未提交的数据,之后对方回滚 T1 把余票 10→8(未提交),T2 读了 8;T1 回滚,实际余票还是 10 基于错误数据做决策
不可重复读 同一事务两次读同一数据,结果不同 T3 第一次读余票 10,T1 改成 8 并提交,T3 第二次读变成 8 同一事务内数据不一致
丢失更新 两个事务都基于旧值修改,后者覆盖前者 T1、T2 都读余票 10,T1 改成 8 提交,T2 基于 10 改成 7 提交,最终=7 T1 的更新丢了

3.4 调度与串行化

是什么?

  • 调度(Schedule):多个事务操作的执行序列。

  • 串行调度 :一个事务全跑完再跑下一个,一定正确但效率低。

  • 冲突可串行化:可以通过交换非冲突操作,等价于某个串行调度的并发调度。

为什么需要?

并发执行是随机的,不是所有随机调度都正确。需要一个判断标准来衡量并发调度是否安全。

怎么用?

判断方法:优先图法

  1. 找出所有冲突操作对(不同事务、同一数据、至少一个写)。

  2. 冲突操作 Ti 在前 → Tj 在后,画边 Ti → Tj。

  3. 无环 = 冲突可串行化 (正确);有环 = 不正确

例子:调度 S = r1(A) w2(A) r2(B) w1(B) 冲突:r1(A)↔w2(A) 得 T1→T2;r2(B)↔w1(B) 得 T2→T1 优先图成环(T1→T2→T1),不是冲突可串行化。

3.5 封锁机制

是什么?

封锁 = 访问数据前先加锁,阻止其他事务以冲突方式同时访问。

两种基本锁

锁类型 符号 特点 相容性
共享锁 S 锁 可读不可写 多个 S 锁可以共存
排他锁 X 锁 可读可写 X 锁与任何锁都不相容

为什么需要封锁?

防止并发冲突的根本手段------"占着茅坑不让人乱来"。

怎么用?

两阶段封锁协议(2PL)

复制代码
 增长阶段:只能加锁,不能解锁
     ↓
 封锁点(最后申请锁的时刻)
     ↓
 缩减阶段:只能解锁,不能加新锁

核心规则:所有锁必须在事务结束前申请完毕,之后只能释放。

为什么能保证冲突可串行化?封锁点定义了一个全序,按封锁点排序执行等价于某个串行调度。

改进协议

协议 改进点 解决什么问题
严格两阶段封锁 X 锁必须等事务提交后才释放 避免读脏数据、避免级联回滚
强两阶段封锁 所有锁(S锁和X锁)提交后才释放 按提交顺序串行化,商用数据库广泛使用

3.6 实际数据库中的并发控制

数据库 方法
SQL Server 严格两阶段封锁 + 行级/页级/表级锁 + 意向锁
MySQL (InnoDB) MVCC(多版本并发控制)+ 两阶段锁
Oracle MVCC + 行级锁

MVCC 核心思想:保存数据的多个版本,读不加锁,写加锁,读操作读历史版本快照。大幅降低锁竞争。


四、恢复与备份

4.1 数据库故障分类

故障类型 是什么 例子
事务故障 事务没跑完就停了 用户取消、代码错误(除以零、约束违反)
系统故障 系统停机,内存丢了,磁盘完好 断电、操作系统崩溃、CPU 故障
介质故障 存储设备物理损坏 硬盘坏道、磁盘损坏、自然灾害
其他故障 人为或外部破坏 黑客入侵、误删表、病毒篡改

4.2 恢复机制

是什么?

恢复 = 数据库故障后,把数据恢复到正确状态的过程。

核心手段是两个操作:

操作 是什么 针对什么场景
UNDO 撤销事务已做的所有更新 事务未完成(未提交),需要回滚到开始前状态
REDO 重做事务已提交的更新 事务已提交,但更新可能还在内存没写磁盘

为什么需要?

保证原子性和持久性

  • 原子性要求:没完成的事务,已经改的数据必须"吐出来"(UNDO)。

  • 持久性要求:已完成的交易,就算天塌了也得保留(REDO)。

怎么用?

基于日志的恢复

日志是记录所有事务操作的追加文件,存在稳固存储器 (如磁盘),遵循先写日志规则(WAL)

日志记录格式

复制代码
 <Ti, START>           -- 事务开始
 <Ti, X, V1, V2>       -- 事务Ti把数据X从V1改成V2(V1=前映像,V2=后映像)
 <Ti, COMMIT>          -- 事务提交
 <Ti, ROLLBACK>        -- 事务回滚
 <Checkpoint L>        -- 检查点,L=活跃事务列表

恢复策略按故障类型

故障类型 恢复策略 为什么
事务故障 UNDO 该事务所有更新 没提交的事务不能留
系统故障 UNDO 所有未完成事务 + REDO 所有已提交事务 内存丢了,已提交的要做持久,未完成的要撤销
介质故障 装最新备份 + REDO 备份后已提交事务 磁盘坏了,先恢复到备份点,再用日志补后续

恢复流程(无检查点)

复制代码
 1. 扫描日志,确定:
    - REDO集 = 日志中有<COMMIT>的事务
    - UNDO集 = 日志中只有<START>没有<COMMIT>的事务
 2. UNDO阶段:从日志尾反向扫描,用前映像(V1)恢复
 3. REDO阶段:从日志头正向扫描,用后映像(V2)重做

关键规则必须先 UNDO,再 REDO。如果顺序反过来,REDO 的结果可能被 UNDO 的前映像覆盖掉。

4.3 检查点(Checkpoint)

是什么?

检查点 = 周期性执行的一个"快照"操作,目的是缩短故障恢复时的日志扫描范围。

为什么需要?

没有检查点,每次恢复都要从头到尾扫描整个日志,日志越长恢复越慢。检查点把日志切成段,只需扫最近一段。

怎么用?

写检查点时做的三件事

  1. 把日志缓冲区的所有日志记录写到磁盘。

  2. 把数据缓冲区的所有更新块写到磁盘。

  3. 在日志中写入 <Checkpoint L>,L 是当前所有未提交事务列表。

引入检查点后的恢复策略

复制代码
 [检查点] ---- [T1提交] ---- [T2开始] ---- [故障点]
      ↑                                    ↑
    扫描起点                            恢复终点
事务情况 恢复操作
检查点前已提交 无需恢复(检查点已刷盘)
检查点前开始,故障前已提交 REDO
检查点前开始,故障时未结束 UNDO
检查点后开始,故障前已提交 REDO
检查点后开始,故障时未结束 UNDO

4.4 备份

是什么?

备份 = 数据库的副本,用于介质故障后恢复。

为什么需要?

介质故障(硬盘损坏)时,日志和数据库文件可能一起没了,单靠日志不够,必须有备份副本作为恢复的起点。

怎么用?

备份类型对比

类型 是什么 特点
静态备份 无事务运行时备份 副本一致,但需暂停业务
动态备份 和事务并发时备份 不影响业务,但必须配合日志
全备份 备份整个数据库 恢复简单,耗时长、占空间
增量备份 只备份上次备份后更新的数据 省时间省空间,恢复需按顺序应用

介质故障恢复步骤

  1. 装入最新备份副本。

  2. 从备份时刻起扫描日志。

  3. REDO 备份后所有已提交事务。

  4. 数据库恢复到故障前一致状态。


五、知识脉络图

复制代码
 事务管理与恢复
     │
     ├── 事务(Transaction)
     │       ├── 是什么:完整逻辑的操作序列
     │       ├── 为什么:保证业务操作不"半成品"
     │       └── 怎么用:BEGIN → 操作 → COMMIT/ROLLBACK
     │
     ├── ACID 特性
     │       ├── A(原子性)→ 恢复管理(UNDO)保证
     │       ├── C(一致性)→ 应用逻辑 + 完整性约束保证
     │       ├── I(隔离性)→ 并发控制(封锁协议)保证
     │       └── D(持久性)→ 恢复管理(REDO + 日志)保证
     │
     ├── 并发控制
     │       ├── 为什么:提高吞吐量、减少响应时间
     │       ├── 问题:读脏数据、不可重复读、丢失更新
     │       ├── 标准:冲突可串行化(优先图判断)
     │       └── 手段:封锁(S锁/X锁)+ 两阶段封锁协议
     │
     └── 恢复与备份
             ├── 故障分类:事务故障、系统故障、介质故障
             ├── 核心操作:UNDO(撤销)+ REDO(重做)
             ├── 依据:日志 + 先写日志规则(WAL)
             ├── 优化:检查点(缩短恢复扫描范围)
             └── 兜底:备份(全量/增量,静态/动态)

六、一句话记忆

概念 一句话
事务 打包操作,要么全成,要么全撤
原子性 做一半出事了,把已经改的吐出来(UNDO)
持久性 提交了就必须永远留着,断电也不怕(REDO + 日志)
隔离性 多人同时干活,互相别打扰(封锁协议)
并发控制 让大家一起干活,但结果要像排队干的一样正确
冲突可串行化 并发调度的结果等价于某个排队顺序
两阶段封锁 前半程只能加锁,后半程只能解锁
检查点 定期存档,故障后不用从头再来
先写日志 先记账,再改数据,改砸了有账本可查

总结基于《数据库系统概论》第10章导学材料整理

相关推荐
lifewange4 小时前
Redis 集合(Set)运算完全指南
数据库·chrome·redis
TDengine (老段)4 小时前
TDengine RAFT共识协议 — 选举、日志复制、快照与仲裁
android·大数据·数据库·物联网·架构·时序数据库·tdengine
Full Stack Developme5 小时前
Spring Boot 事务管理完整教程
java·数据库·spring boot
m0_702036536 小时前
mysql如何通过索引减少行锁范围_mysql索引与加锁逻辑
jvm·数据库·python
qxwlcsdn7 小时前
如何用 IndexedDB 存储从 API 获取的超大列表并实现二级索引
jvm·数据库·python
phltxy7 小时前
Redis 主从复制
java·数据库·redis
2301_809244537 小时前
C#怎么使用协变和逆变 C#泛型中的in和out关键字协变逆变是什么意思怎么用【语法】
jvm·数据库·python
知识汲取者7 小时前
巨量引擎营销 API 完整文档
开发语言·数据库·python
j7~8 小时前
【MYSQL】在Centos7和ubuntu22.04环境下安装
数据库·c++·mysql·ubuntu·centos