事务是数据库管理系统中处理并发操作的基本单位,其核心在于保证数据操作的可靠性与一致性。数据库通过ACID特性 定义事务的行为规范,而redo log
、undo log
和MVCC
则是实现这些特性的关键技术。
一、事务的ACID特性概述
事务的四大特性是数据库可靠性的基石,具体定义如下:
- 原子性(Atomicity):事务是不可分割的最小单位,要么全部执行成功,要么全部失败回滚,不存在部分执行的状态。例如,转账操作中"扣款"和"入账"必须同时成功或同时失败。
- 一致性(Consistency):事务执行前后,数据库从一个一致状态转换到另一个一致状态,满足预设的业务规则(如余额不能为负)。
- 隔离性(Isolation):多个并发事务之间相互隔离,一个事务的操作不会被其他事务干扰,避免脏读、不可重复读、幻读等问题。
- 持久性(Durability):事务提交后,其修改会永久保存到数据库中,即使系统崩溃也不会丢失。
二、原子性与一致性的实现:undo log(回滚日志)
undo log
是保障事务原子性的核心机制,同时为一致性提供基础支持,也是MVCC实现的重要依赖。
(一)核心作用
- 支持事务回滚 :当事务执行失败或主动触发
ROLLBACK
时,undo log
记录的反向操作可撤销事务的所有修改,确保原子性。 - 支撑MVCC:为并发读操作提供数据的历史版本,保证隔离性的同时,间接维护了数据的一致性。
(二)日志本质与示例
undo log
是逻辑日志,记录"与原操作相反的逻辑指令",而非物理修改(如内存地址或磁盘块变化)。
- 若执行
INSERT
,undo log
记录DELETE
(包含插入的完整数据); - 若执行
UPDATE
,undo log
记录"将字段恢复为修改前值"的UPDATE
指令; - 若执行
DELETE
,undo log
记录INSERT
(包含删除前的完整数据)。
(三)生命周期与存储
- 生成 :事务执行过程中实时生成,每条修改操作都会对应一条或多条
undo log
。 - 使用 :
- 事务回滚时,逐条执行
undo log
的反向操作,撤销修改; - MVCC中,快照读通过
undo log
访问历史版本(如可重复读隔离级别下的非阻塞读)。
- 事务回滚时,逐条执行
- 销毁 :事务提交后,
undo log
不会立即删除(需保留供MVCC读取),由后台Purge
线程异步回收(当历史版本不再被任何事务需要时)。 - 存储 :存放在
rollback segment
(回滚段)中,每个回滚段包含1024个undo log segment
,可通过innodb_rollback_segments
参数配置数量。
三、持久性的实现:redo log(重做日志)
redo log
是保障事务持久性的关键,通过预写日志机制(WAL)确保已提交事务的数据不丢失。
(一)核心作用
当数据库崩溃时,若内存中的修改(脏页)尚未刷写到数据文件,可通过redo log
重新执行修改操作,恢复已提交事务的结果,确保持久性。
(二)组成与工作流程
redo log
由内存和磁盘两部分组成,配合缓冲池(Buffer Pool)完成数据修改的持久化:
- redo log buffer:内存中的临时缓冲区,暂存事务的修改日志。
- redo log file :磁盘上的持久化文件(如
ib_logfile0
、ib_logfile1
),按顺序追加写入。
工作流程:
- 事务执行
UPDATE/INSERT/DELETE
时,先修改缓冲池(Buffer Pool)中的数据页(生成脏页); - 同时,将修改的物理操作(如"在某数据页的某位置写入某值")记录到
redo log buffer
; - 事务提交时,
redo log buffer
中的日志根据参数innodb_flush_log_at_trx_commit
配置刷写到redo log file
(确保日志持久化); - 后台线程(如
master thread
)定期将缓冲池中的脏页异步刷写到数据文件(如.ibd
)。
(三)为何比直接刷脏页更高效?
对比维度 | 直接刷脏页 | redo log刷盘 |
---|---|---|
磁盘IO类型 | 随机IO(需定位数据页位置,频繁寻道) | 顺序IO(日志文件固定大小,循环覆盖写入) |
数据量 | 每次刷写完整数据页(通常16KB) | 仅记录修改的物理操作(日志体积小) |
性能影响 | 频繁IO导致性能低下 | 顺序写+小体积日志,性能大幅提升 |
崩溃恢复 | 未刷盘的脏页丢失,无法恢复 | 可通过redo log重放修改,完整恢复 |
(四)刷盘时机控制
参数innodb_flush_log_at_trx_commit
决定事务提交时redo log buffer
的刷盘策略:
0
:每秒由后台线程将redo log buffer
刷盘(崩溃可能丢失1秒内的事务);1
(默认):事务提交时立即将redo log buffer
刷盘并同步到磁盘(最安全,性能略低);2
:事务提交时将redo log buffer
刷到操作系统缓存,由操作系统每秒同步到磁盘(崩溃可能丢失操作系统缓存中的数据)。
四、隔离性的实现:MVCC(多版本并发控制)
隔离性要求多个并发事务的操作相互隔离,MVCC
通过维护数据的多版本,实现读写不阻塞,是InnoDB默认的隔离性实现机制(配合锁机制解决写写冲突)。
(一)MVCC的核心目标
- 允许读操作不阻塞写操作,写操作不阻塞读操作(读写并行);
- 在不同隔离级别下,通过控制数据版本的可见性,避免脏读、不可重复读等问题。
(二)快照读与当前读:MVCC中的两种读方式
MVCC通过两种读方式实现读写并行,具体区别如下:
1. 快照读(Snapshot Read)
-
定义 :不加锁的非阻塞读,读取的是数据的"历史版本"(而非最新版本),版本由事务首次读取时生成的快照(通过
ReadView
确定可见性)决定。 -
核心特点 :
- 无锁竞争,避免读写冲突,提升并发性能;
- 版本依赖于事务隔离级别和
ReadView
生成时机; - 非阻塞性,即使数据被其他事务锁定,仍可读取历史版本。
-
适用场景 :简单查询语句(不加锁的
SELECT
),例如:sqlSELECT id, name FROM user WHERE age > 18; -- 快照读,无锁
2. 当前读(Current Read)
- 定义:加锁的阻塞读,读取的是数据的"最新版本",通过加锁(共享锁或排他锁)确保数据一致性,阻塞其他事务的冲突操作。
- 核心特点 :
- 加锁机制保证读取最新版本,避免并发修改冲突;
- 阻塞性,若记录被其他事务加排他锁,会等待锁释放。
- 适用场景 :
-
写操作:
INSERT
、UPDATE
、DELETE
(自动加排他锁);sqlUPDATE user SET name = 'new' WHERE id = 1; -- 当前读,加排他锁
-
手动加锁的查询:
sqlSELECT id FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 加共享锁 SELECT id FROM user WHERE id = 1 FOR UPDATE; -- 加排他锁
-
(三)MVCC的三大组成部分
1. 数据行的隐藏字段
InnoDB为每行数据添加三个隐藏字段,用于标记版本和关联回滚日志:
DB_TRX_ID
:最近修改该记录的事务ID(事务ID是自增的,新事务ID更大);DB_ROLL_PTR
:回滚指针,指向undo log
中该记录的上一个版本(形成版本链);DB_ROW_ID
:若表无主键,自动生成的隐藏主键,用于唯一标识记录。
2. undo log版本链
事务修改数据时,会生成undo log
并通过DB_ROLL_PTR
串联成版本链,最新版本在链头,历史版本依次向后。
示例:
- 事务10插入一条记录,
DB_TRX_ID=10
,DB_ROLL_PTR=NULL
(无历史版本); - 事务20更新该记录,生成
undo log
(记录事务10的版本),新记录DB_TRX_ID=20
,DB_ROLL_PTR
指向事务10的undo log
; - 事务30再次更新,生成新的
undo log
(记录事务20的版本),DB_TRX_ID=30
,DB_ROLL_PTR
指向事务20的undo log
。
3. ReadView(读视图)
ReadView
是快照读的依据,记录当前系统中活跃的事务ID,用于判断版本链中哪个版本对当前事务可见。
ReadView包含四个核心字段:
m_ids
:当前活跃事务ID的集合(生成快照时未提交的事务);min_trx_id
:活跃事务中最小的事务ID;max_trx_id
:下一个将被分配的事务ID(当前最大事务ID+1);creator_trx_id
:生成该ReadView
的事务ID。
(四)版本可见性判断规则
当事务执行快照读时,通过ReadView
检查版本链中记录的DB_TRX_ID
,判断是否可见:
- 若
DB_TRX_ID == creator_trx_id
:可见(当前事务修改的记录); - 若
DB_TRX_ID < min_trx_id
:可见(该事务已提交,且早于所有活跃事务); - 若
DB_TRX_ID >= max_trx_id
:不可见(该事务在ReadView
生成后才开启); - 若
min_trx_id ≤ DB_TRX_ID < max_trx_id
:- 若
DB_TRX_ID
不在m_ids
中:可见(事务已提交); - 若
DB_TRX_ID
在m_ids
中:不可见(事务未提交,属于活跃事务)。
- 若
(五)不同隔离级别的MVCC实现
- 读已提交(Read Committed, RC) :每次执行
select
时生成新的ReadView
,因此能看到其他事务已提交的最新修改(避免脏读,但可能出现不可重复读)。 - 可重复读(Repeatable Read, RR) :事务内第一次
select
时生成ReadView
,后续select
复用该ReadView
,因此多次读取结果一致(避免不可重复读)。
五、ACID特性的协同实现
- 原子性 :由
undo log
实现,事务失败时通过回滚日志撤销所有修改。 - 持久性 :由
redo log
实现,通过预写日志确保已提交事务的修改不会因崩溃丢失。 - 隔离性 :由
MVCC
(解决读写冲突,通过快照读和当前读实现)和锁机制(解决写写冲突)共同实现,不同隔离级别通过ReadView
生成时机和锁粒度控制隔离程度。 - 一致性:是原子性、持久性、隔离性共同作用的结果,同时依赖应用层的业务逻辑(如约束检查、触发器等)。
总结
redo log
、undo log
和MVCC
是InnoDB事务机制的三大支柱:
redo log
保障"已提交事务不丢失",是持久性的核心;undo log
保障"事务要么全成要么全退",是原子性的核心,同时支撑MVCC的历史版本;MVCC
通过多版本、ReadView
及"快照读/当前读"的区分,实现高效的读写并行,是隔离性的核心。
三者协同工作,最终实现了事务的ACID特性,使数据库在高并发场景下既能保证数据一致性,又能维持高效的读写性能。