MySQL学习笔记 ---- redo log、undo log及MVCC详解

发布于:2025-08-16 ⋅ 阅读:(12) ⋅ 点赞:(0)

事务是数据库管理系统中处理并发操作的基本单位,其核心在于保证数据操作的可靠性与一致性。数据库通过ACID特性定义事务的行为规范,而redo logundo logMVCC则是实现这些特性的关键技术。

一、事务的ACID特性概述

事务的四大特性是数据库可靠性的基石,具体定义如下:

  • 原子性(Atomicity):事务是不可分割的最小单位,要么全部执行成功,要么全部失败回滚,不存在部分执行的状态。例如,转账操作中“扣款”和“入账”必须同时成功或同时失败。
  • 一致性(Consistency):事务执行前后,数据库从一个一致状态转换到另一个一致状态,满足预设的业务规则(如余额不能为负)。
  • 隔离性(Isolation):多个并发事务之间相互隔离,一个事务的操作不会被其他事务干扰,避免脏读、不可重复读、幻读等问题。
  • 持久性(Durability):事务提交后,其修改会永久保存到数据库中,即使系统崩溃也不会丢失。

二、原子性与一致性的实现:undo log(回滚日志)

undo log是保障事务原子性的核心机制,同时为一致性提供基础支持,也是MVCC实现的重要依赖。

(一)核心作用

  1. 支持事务回滚:当事务执行失败或主动触发ROLLBACK时,undo log记录的反向操作可撤销事务的所有修改,确保原子性。
  2. 支撑MVCC:为并发读操作提供数据的历史版本,保证隔离性的同时,间接维护了数据的一致性。

(二)日志本质与示例

undo log逻辑日志,记录“与原操作相反的逻辑指令”,而非物理修改(如内存地址或磁盘块变化)。

  • 若执行INSERTundo log记录DELETE(包含插入的完整数据);
  • 若执行UPDATEundo log记录“将字段恢复为修改前值”的UPDATE指令;
  • 若执行DELETEundo 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_logfile0ib_logfile1),按顺序追加写入。

工作流程

  1. 事务执行UPDATE/INSERT/DELETE时,先修改缓冲池(Buffer Pool)中的数据页(生成脏页);
  2. 同时,将修改的物理操作(如“在某数据页的某位置写入某值”)记录到redo log buffer
  3. 事务提交时,redo log buffer中的日志根据参数innodb_flush_log_at_trx_commit配置刷写到redo log file(确保日志持久化);
  4. 后台线程(如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),例如:
    SELECT id, name FROM user WHERE age > 18;  -- 快照读,无锁
    
2. 当前读(Current Read)
  • 定义:加锁的阻塞读,读取的是数据的“最新版本”,通过加锁(共享锁或排他锁)确保数据一致性,阻塞其他事务的冲突操作。
  • 核心特点
    • 加锁机制保证读取最新版本,避免并发修改冲突;
    • 阻塞性,若记录被其他事务加排他锁,会等待锁释放。
  • 适用场景
    • 写操作:INSERTUPDATEDELETE(自动加排他锁);
      UPDATE user SET name = 'new' WHERE id = 1;  -- 当前读,加排他锁
      
    • 手动加锁的查询:
      SELECT 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=10DB_ROLL_PTR=NULL(无历史版本);
  • 事务20更新该记录,生成undo log(记录事务10的版本),新记录DB_TRX_ID=20DB_ROLL_PTR指向事务10的undo log
  • 事务30再次更新,生成新的undo log(记录事务20的版本),DB_TRX_ID=30DB_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,判断是否可见:

  1. DB_TRX_ID == creator_trx_id:可见(当前事务修改的记录);
  2. DB_TRX_ID < min_trx_id:可见(该事务已提交,且早于所有活跃事务);
  3. DB_TRX_ID >= max_trx_id:不可见(该事务在ReadView生成后才开启);
  4. min_trx_id ≤ DB_TRX_ID < max_trx_id
    • DB_TRX_ID不在m_ids中:可见(事务已提交);
    • DB_TRX_IDm_ids中:不可见(事务未提交,属于活跃事务)。

(五)不同隔离级别的MVCC实现

  • 读已提交(Read Committed, RC):每次执行select时生成新的ReadView,因此能看到其他事务已提交的最新修改(避免脏读,但可能出现不可重复读)。
  • 可重复读(Repeatable Read, RR):事务内第一次select时生成ReadView,后续select复用该ReadView,因此多次读取结果一致(避免不可重复读)。

五、ACID特性的协同实现

  • 原子性:由undo log实现,事务失败时通过回滚日志撤销所有修改。
  • 持久性:由redo log实现,通过预写日志确保已提交事务的修改不会因崩溃丢失。
  • 隔离性:由MVCC(解决读写冲突,通过快照读和当前读实现)和锁机制(解决写写冲突)共同实现,不同隔离级别通过ReadView生成时机和锁粒度控制隔离程度。
  • 一致性:是原子性、持久性、隔离性共同作用的结果,同时依赖应用层的业务逻辑(如约束检查、触发器等)。

总结

redo logundo logMVCC是InnoDB事务机制的三大支柱:

  • redo log保障“已提交事务不丢失”,是持久性的核心;
  • undo log保障“事务要么全成要么全退”,是原子性的核心,同时支撑MVCC的历史版本;
  • MVCC通过多版本、ReadView及“快照读/当前读”的区分,实现高效的读写并行,是隔离性的核心。

三者协同工作,最终实现了事务的ACID特性,使数据库在高并发场景下既能保证数据一致性,又能维持高效的读写性能。


网站公告

今日签到

点亮在社区的每一天
去签到