一、事务与ACID特性回顾
事务是数据库中进行一系列操作的逻辑单元,这些操作要么全部成功,要么全部失败。其核心特性被称为ACID。
- 原子性
事务被视为一个不可分割的原子操作,要么全部完成,要么回滚到执行前的状态。
- 一致性
事务执行前后,数据库都处于一致的状态,不会违反任何数据完整性约束。
- 隔离性
多个并发事务的执行互不干扰,数据库提供了不同的隔离级别来控制这种干扰的程度。
- 持久性
一旦事务提交,其所作的修改就会永久地保存在数据库中。
二、InnoDB事务实现的核心组件概览
在MySQL的数据库/中间件生态中,InnoDB存储引擎通过一套精密的机制实现ACID:
- Redo Log:保障持久性。
- Undo Log:保障原子性,并为MVCC提供基础。
- 锁机制:实现隔离性的一部分。
- MVCC:实现隔离性的另一部分,支持非阻塞读。
- 两阶段提交:协调Redo Log与Binlog,确保数据一致性。
整体思想遵循 WAL:先写日志,后写数据,通过日志、版本控制和锁来协同保障事务的ACID特性。
三、原子性的基石:Undo Log
原子性要求事务可以回滚,这依赖于 Undo Log。
1. Undo Log的作用
它记录了如何将数据恢复到旧版本。例如执行UPDATE时,会记录被修改字段的原始值。
主要承担两个职责:
- 事务回滚:当执行
ROLLBACK时,InnoDB会根据Undo Log进行逆向操作。
- 构建MVCC版本链:每一行数据的修改历史通过Undo Log串联起来,形成版本链,使得其他事务可以进行“快照读”。
2. Undo Log的类型
- Insert Undo Log:记录
INSERT操作,仅在事务回滚时需要,提交后可快速清理。
- Update Undo Log:记录
UPDATE和DELETE操作,除用于回滚外,还可能被其他事务的MVCC读取,因此清理时机由系统根据活跃事务情况决定。
四、持久性的保障:Redo Log与WAL
为了避免频繁、缓慢的随机磁盘IO,InnoDB采用 WAL 机制:Write-Ahead Logging。
1. 核心流程
- 事务修改数据时,首先在内存的
Buffer Pool中操作,生成脏页。
- 同时,将这次物理修改(如表空间号、页号、具体修改内容)记录到 Redo Log Buffer。
- 事务提交时,根据策略(如
innodb_flush_log_at_trx_commit),保证相关的Redo Log至少被刷新到磁盘。
- 内存中的脏页则由后台线程在适当时机异步刷回磁盘。
只要Redo Log成功落盘,即使此时数据页未刷盘,宕机后也能通过重放Redo Log恢复数据,从而保证了持久性。
2. 崩溃恢复
MySQL重启时,会扫描Redo Log,从最后一个检查点开始,重新应用所有已提交但数据未落盘的事务修改。
五、隔离性的实现:锁与MVCC
隔离性通过 锁 和 MVCC 协同实现。
1. 锁机制
锁主要用于处理写-写冲突,确保并发的修改操作有序进行。InnoDB的行级锁主要包括:
- 记录锁:锁定单条记录。
- 间隙锁:锁定记录之间的间隙,防止插入。
- 临键锁:记录锁与间隙锁的组合,是InnoDB在默认隔离级别下防止幻读的主要手段。
- 意向锁:表级锁,用于快速判断表中是否存在行锁。
2. MVCC机制
MVCC使得普通的SELECT读操作可以不加锁,通过读取历史版本来实现非阻塞读,极大提升了并发性能。
InnoDB每行记录包含两个隐藏字段:
trx_id:最后一次修改该行的事务ID。
roll_pointer:指向该行上一个版本的Undo Log记录指针。
不同的事务根据其启动时机和隔离级别,会基于Undo Log构成的版本链,决定读取哪个版本的数据。
六、四种隔离级别的具体实现
MySQL支持四种隔离级别,其实现差异主要体现在MVCC和锁的使用上。
-
读未提交
直接读取数据页上的最新值,基本不使用MVCC,存在脏读问题。
-
读已提交
每次执行SELECT时都会生成一个独立的“读视图”。因此,在同一事务内,两次相同的查询可能读到不同的数据(不可重复读)。
-
可重复读
在事务开始时生成一个“读视图”,并在整个事务周期内使用它,从而保证多次读取的一致性。对于快照读,通过MVCC避免幻读;对于当前读,则通过临键锁来防止其他事务插入新数据。这是InnoDB的默认隔离级别。
-
串行化
所有读操作都会加锁,相当于强制事务串行执行,隔离级别最高,但并发性能最差。
七、一致性的达成
一致性是事务追求的最终目标,它是原子性、隔离性、持久性共同作用下的结果,同时也依赖于:
- 数据库本身定义的约束(主键、外键、唯一键等)。
- 应用程序正确的业务逻辑。
八、关键协作:Redo与Binlog的两阶段提交
为了保证存储引擎层(Redo Log)和Server层(Binlog)在崩溃恢复和主从复制时的一致性,InnoDB采用两阶段提交。
- Prepare阶段:InnoDB将事务的Redo Log写入磁盘,并标记状态为
prepare。
- Commit阶段:写入Binlog,随后将Redo Log的状态修改为
commit。
崩溃恢复时,如果发现Redo Log处于prepare状态,则会检查对应的Binlog是否完整。如果完整,则提交事务;否则,回滚事务。
九、锁的释放与死锁
InnoDB的加锁遵循两阶段锁协议:在事务执行过程中逐步获取锁,在事务提交或回滚时统一释放所有锁。
这种机制可能引发死锁。InnoDB有后台的死锁检测机制,一旦发现死锁,会选择回滚代价最小的事务来打破僵局。
十、崩溃恢复流程简述
数据库意外宕机后重启,恢复流程保证了ACID:
- 重放Redo Log,将所有已提交事务的修改重新应用到数据页上(重做)。
- 通过Undo Log,回滚所有未提交事务的修改(回滚)。
最终,数据库恢复到一致性状态:已提交的事务全部生效,未提交的事务如同从未发生。
十一、总结:InnoDB事务实现的整体脉络
理解InnoDB事务的实现,可以构建如下知识框架:
- 持久性靠Redo:WAL机制确保先写日志,数据可恢复。
- 原子性靠Undo:记录旧值,支持回滚与版本链。
- 隔离性靠MVCC+锁:读写分离,MVCC实现无锁快照读,锁控制并发写。
- 一致性靠协作:上述机制与数据库约束、后端与架构层的正确逻辑共同作用。
- 协调靠2PC:两阶段提交确保Redo与Binlog的最终一致,这是保障网络与系统数据可靠性的关键技术点。
掌握这些核心原理,有助于在开发中更精准地使用事务、诊断并发问题并优化数据库性能。
|