MySQL InnoDB事务实现
InnoDB实现事务主要依赖于以下三大核心技术和一个保证:
- 两大日志:Redo Log(重做日志)和 Undo Log(回滚日志)
- 锁机制:实现隔离性的核心
- 多版本并发控制(MVCC):实现高并发读写的关键技术
- ACID特性的具体实现保证
下面我们来详细拆解InnoDB是如何通过这些技术来实现事务的ACID特性的。
1. 核心组件与技术
1.1 Redo Log(重做日志) - 保证持久性 (Durability)
- 是什么:一种物理日志,记录的是“在某个数据页上做了什么修改”。它是顺序写入的固定大小的循环文件。
- 为什么需要:如果每次事务提交都直接随机写入磁盘(刷脏页),性能会非常差。Redo Log提供了Write-Ahead Logging (WAL) 机制,即先写日志,再写磁盘。
- 工作流程:
- 当事务执行修改操作(如UPDATE)时,InnoDB会先将数据页从磁盘加载到Buffer Pool(内存)中。
- 在内存中修改数据,这个被修改了但还没写回磁盘的数据页称为“脏页 (Dirty Page)”。
- 同时,InnoDB会将本次修改的内容顺序写入到Redo Log Buffer(内存)中。
- 在事务提交时(
COMMIT
),必须将Redo Log Buffer中的相关日志刷新到磁盘的Redo Log文件里(默认策略,可通过innodb_flush_log_at_trx_commit
调整)。 - 之后,数据库会在合适的时机(如系统空闲、日志写满时)将Buffer Pool中的脏页刷新到磁盘的数据文件中。
- 崩溃恢复:如果数据库在脏页刷盘前崩溃了,重启后InnoDB会读取Redo Log文件,将那些已经提交但尚未应用到数据文件的事务重做(Redo)一遍,从而保证了持久性。
简单比喻:酒馆掌柜有一个账本(数据文件)和一个流水簿(Redo Log)。客人买单(事务提交),掌柜不会立刻去翻厚厚的账本,而是在流水簿上记一笔“XX号桌收入100元”。打烊后,掌柜再根据流水簿一笔笔去更新账本。即使掌柜打�前晕倒了,醒来后看看流水簿也知道哪些收入是确定的。
1.2 Undo Log(回滚日志) - 保证原子性 (Atomicity)
- 是什么:一种逻辑日志,记录与事务操作相反的逻辑。比如INSERT一条记录,Undo Log就记录一个DELETE;DELETE一条记录,就记录一个INSERT;UPDATE则记录一个反向的UPDATE。
- 为什么需要:用于事务的回滚和MVCC。
- 工作流程:
- 在事务中对数据进行任何修改之前,InnoDB会先生成对应的Undo Log记录,并将其写入Undo Log段(位于共享表空间中)。
- 如果事务执行失败或显式调用
ROLLBACK
,InnoDB会利用Undo Log中的反向记录,将数据恢复到事务开始前的状态,从而实现回滚,保证了原子性。
- 其他作用:Undo Log还被MVCC用来构建数据的历史版本。
1.3 锁机制 (Locking) - 保证隔离性 (Isolation) 的基础
锁是保证隔离性的最直接方式。InnoDB实现了行级锁,大大提高了并发性能。
- 共享锁 (S Lock):允许事务读一行数据。其他事务可以同时获取共享锁,但不能获取排他锁。
- 排他锁 (X Lock):允许事务更新或删除一行数据。其他事务无法获取该行的任何锁。
- 意向锁 (Intention Locks):表级锁,表明事务稍后会对表中的行施加哪种类型的锁(共享或排他)。用于快速判断表中是否存在行锁,避免逐行检查。
通过不同的锁策略(如读未提交、读已提交、可重复读、串行化),实现了不同级别的隔离性。但完全依赖锁会导致性能问题(如读写阻塞),因此InnoDB引入了MVCC。
1.4 多版本并发控制 (MVCC) - 实现高并发读写的关键
MVCC是InnoDB实现非锁定读(快照读)的基石,极大提升了在READ COMMITTED
和REPEATABLE READ
隔离级别下的并发性能。
- 核心思想:为每一行数据存储多个版本。当一个事务要读取数据时,它会看到在它开始之前已经提交的数据版本,而不会看到它开始之后其他事务未提交或已提交的修改。
- 实现依赖:
- 隐藏字段:每行数据有两个隐藏字段。
DB_TRX_ID
(6字节):记录最后修改(INSERT/UPDATE)此行数据的事务ID。DB_ROLL_PTR
(7字节):回滚指针,指向该行数据在Undo Log中的上一个历史版本。
- Read View:在事务执行快照读时产生的读视图。
- 它定义了当前事务能看到哪些数据版本。
- 其主要属性包括:
m_ids
(当前活跃的事务ID集合)、min_trx_id
(最小活跃事务ID)、max_trx_id
(系统下一个将分配的事务ID)、creator_trx_id
(创建该Read View的事务ID)。
- 隐藏字段:每行数据有两个隐藏字段。
- 工作流程(可重复读为例):
- 当一个事务第一次执行
SELECT
时,会生成一个Read View。 - 当读取某一行时,会检查该行数据的
DB_TRX_ID
。 - 如果
DB_TRX_ID
小于min_trx_id
,说明该版本在事务开始前已提交,可见。 - 如果
DB_TRX_ID
大于等于max_trx_id
,说明该版本在事务开始后才生成,不可见。 - 如果
DB_TRX_ID
在m_ids
中,说明该版本由当前活跃事务修改,不可见;如果不在,说明已提交,可见。 - 如果不可见,则通过
DB_ROLL_PTR
找到Undo Log中的上一个版本,重新执行上述判断,直到找到可见的版本为止。 - 这个Read View会一直用到事务结束,因此保证了可重复读——即在同一事务中,多次读取同一数据,看到的结果是一致的。
- 当一个事务第一次执行
2. ACID特性的实现总结
ACID属性 | 实现技术 |
---|---|
原子性 (Atomicity) | Undo Log。用于事务回滚,如果事务失败,利用Undo Log将数据恢复原状。 |
一致性 (Consistency) | 这是应用和数据库共同的责任。由原子性、隔离性、持久性来保证,同时数据库的约束(如主键、外键、唯一索引)也起到关键作用。 |
隔离性 (Isolation) | 锁 是基础,MVCC 是主要手段。两者结合,在保证数据一致性的前提下,提供了高性能的并发访问。 |
持久性 (Durability) | Redo Log。事务提交时,先写Redo Log,即使系统崩溃,也能通过Redo Log恢复已提交的事务。 |
3. 一个事务的完整生命周期(以UPDATE为例)
- 开始事务:
BEGIN;
- 执行UPDATE:
- 检查数据页是否在Buffer Pool中,不在则从磁盘加载。
- 记录该行数据的Undo Log。
- 在内存(Buffer Pool)中修改该行数据,使其成为脏页。
- 将修改记录写入Redo Log Buffer。
- 对该行数据加排他锁。
- 提交事务:
COMMIT;
- 刷日志:将Redo Log Buffer中的内容刷新到磁盘的Redo Log文件中。
- 刷脏页:后台IO线程会在某个时间点将脏页写入表数据文件。
- 释放锁:事务提交后,释放该行数据上的排他锁。
- 回滚事务:如果执行
ROLLBACK;
- 利用Undo Log执行反向操作,恢复数据。
- 释放锁。
总结
InnoDB通过精巧地结合Redo Log、Undo Log、锁机制和MVCC,高效可靠地实现了事务的所有特性:
- 用Undo Log保证原子性(回滚)。
- 用Redo Log保证持久性(崩溃恢复)。
- 用锁和MVCC共同保证隔离性(并发控制)。
- 最终,三者一起为一致性提供了坚实基础。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论