目录
背景知识:
- MySQL 分为两层 : Server 层(负责 MySQL 功能)和 存储引擎层(负责数据存储)
- Redo Log 和 Undo Log 属于存储引擎层 InnoDB引擎 的日志,其他引擎是没有的
- Binlog 是属于 Server 层的日志
Redo Log(重做日志)
问题引入:
- 如果MySQL的每一次修改数据都需要写入磁盘,那么每一次操作就对应着一次磁盘IO,成本很高。
- 如果MySQL的每一次修改数据都将脏页暂存在内存里,然后延迟异步刷盘,可能系统突然崩溃,导致内存的脏页数据丢失,此时就不能保证事务的持久性。
脏页:内存中被修改,但尚未同步到磁盘的数据页
解决方案:为了解决上述问题,既要保证性能,又要保证持久性,MySQL的设计者就采用了WAL技术,WAL即 Write-Ahead Logging,关键点是 先写日志, 写的就是RedoLog日志
在事务提交时,将日志写入Redo Log磁盘文件,记录下数据页的物理修改。当系统突然崩溃时,脏页数据丢失,可以根据Redo Log找回脏页数据
问题:
那么 直接把脏页数据写入磁盘 和 把日志记录写入磁盘 不都是一次磁盘IO吗?为什么选择后者?
RedoLog 是固定大小的文件,以循环的方式写入,不断覆盖最早的内容
因为 把脏页数据写入磁盘是随机IO,而把日志记录写入RedoLog文件中是顺序IO
顺序IO效率远大于随机IO
记录的内容:数据页的物理修改写入时机:事务提交时用途:保证事务的持久性
假设执行一条 SQL:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
InnoDB 找到 id=1 的数据所在的数据页(假设是页 5)。修改页 5 中的 balance 字段值(从 500 改为 400)。生成一条 redo log,记录:“页 5 的偏移位置 X,值从 500 变为 400”。事务提交时,redo log 被刷盘,但数据页可能仍在内存中(未刷盘)。若此时数据库崩溃,重启后通过 redo log 重放,将页 5 的修改重新应用到磁盘。
Undo Log(回滚日志)
用途:保证事务的原子性 和 支持实现MVCC(多版本并发控制)
保证事务原子性的原理:
Undo Log 是逻辑日志,记录了数据修改操作的“反向操作”,当事务需要回滚时,MySQL可以通过反向操作将数据恢复到事务开始时的状态,从而保证事务的原子性。
比如,如果一个事务修改了一行数据,将某列的值从 100 修改为 200,Undo Log 会记录一个反向操作,表示将 200 恢复为 100,从而撤销这一修改。
支持实现多版本并发控制MVCC 的原理:
Undo Log 记录了数据的版本链
1.版本链的构建:
- 每次修改数据行时,会生成一个 Undo Log 记录,包含:
-
- 旧版本的数据镜像
- 事务 ID(
trx_id):标识生成该版本的事务。 - 回滚指针(
roll_pointer):指向更早版本的 Undo Log。
- 多个版本通过
roll_pointer形成链表结构,称为版本链。
2.MVCC 的读操作:
- 当某事务需要读取数据时,InnoDB 会根据当前事务的视图(
ReadView)的信息与数据版本的trx_id去比较,从而判断哪些版本可见,如果最新版本不可读,则会根据 Undo Log 的版本链去获得上一版本的数据
如何理解MVCC
背景知识:
- MySQL会为每个事务分配一个事务ID,事务ID按事务创建顺序递增
MVCC 是 多版本并发控制,核心思想:维护一个数据的多个版本,使得读写操作没有冲突,避免加锁阻塞
典型应用:MySQL 的 InnoDB 存储引擎通过 MVCC 实现 读已提交 和 可重复读 隔离级别。
MVCC的实现依赖于
- 数据库记录行的隐藏字段 trx_id 事务id 和 roll_pointer 指针
- ReadView读视图
- UndoLog版本链
实现原理:
首先,InnoDB 在事务开启后执行第一个查询时,会创建一个快照(称之为ReadView),这个ReadView包含了以下信息:
m_ids: 活动事务id列表(活动事务指的是已经开始、尚未提交/回滚的事务)
min_trx_id: 最小活动事务idmin_trx_id 是 ReadView 创建时 “所有活跃事务的最小 ID”—— 若某个事务的 ID 比 min_trx_id 还小,说明它在所有活跃事务之前就结束了(已提交 / 回滚),其修改的数据是 “稳定的”
max_trx_id: 最大活动事务id
creator_trx_id: 当前事务id紧接着 InnoDB 会通过查询语句定位到最新版本的数据行,并根据以下规则获取到可以访问的数据版本:
- 如果被访问版本的trx_id 事务id,等于ReadView中的当前事务id,表明当前事务在访问自己修改过的记录,即可以读取到该版本的数据;
- 如果被访问版本的trx_id 事务id,小于ReadView中的最小活动事务id,表明生成该版本的事务在当前事务生成ReadView前已经提交,即可以读取到该版本的数据;
- 如果被访问版本的trx_id 事务id,大于或等于ReadView中的最大活动事务id,表明生成该版本的事务在当前事务生成ReadView后才开启,此时该版本不可以被当前事务访问,需要通过隐藏的回滚指针从undo log中读取历史版本;
- 如果被访问版本的trx_id 事务id,在ReadView的 最小活动事务id 和 最大活动事务id 之间,则需要判断 trx_id 事务id 是否在活动事务列表中?
- 如果在:说明ReadView创建时,创建该版本数据的事务还未提交,因此需要通过回滚指针读取历史版本并返回;
- 如果不在:说明ReadView创建时,创建该版本数据的事务已经提交,所以可以读取到该版本的数据。
读已提交隔离级别下:事务中的每次查询前都会创建一个新的ReadView。新建的ReadView会更新creator_trx_id以外的其余字段,因此不可重复读现象依然存在。
可重复读隔离级别下:只会在事务中的第一次查询时创建ReadView,同一个事务中后续所有的查询共用一个ReadView,由此便解决了不可重复读的问题。
ReadView的生成时机是不同的,这也是两个隔离级别有区别的根本原因
BinLog(二进制日志)
BinLog: 是记录所有数据库表结构变更 以及 表数据修改的二进制日志,不会记录Select和Show操作。
用途:数据备份、灾难恢复、主从同步
写入时机:事务提交时
BinLog的三种格式
MySQL 的BinLog支持三种格式: Statement、Row、Mixed
- Statement
-
- BinLog里面记录的是 SQL 语句原文
- 这种格式有bug,例如使用now()等时间函数,导致主从同步后数据不相同
- Row
-
- BinLog里面记录的是 数据的具体变化
- 例如 会记录修改的表名称、操作类型
-
-
- Insert操作 Row格式会记录新插入的行的 所有字段列的值
- Update操作 Row格式会记录行 更新前后的 所有字段列的值
- Delete操作 Row格式会记录行删除前的 所有字段列的值
-
-
- 优点:可以更好的保证主从数据同步时数据一致性,避免了
STATEMENT格式中可能出现的不一致性问题 - 缺点:
- 优点:可以更好的保证主从数据同步时数据一致性,避免了
-
-
- 需要记录的内容更多,例如批量修改、插入等,就需要把每条记录都记录,存在性能问题
- 文件体积大,在主从同步时,网络IO更高,耗费时间长
-
- Mixed
-
- 由MySQL根据具体情况自主选择使用Statement格式或Row格式来记录
- 默认使用Statement格式,记录SQL语句
- 在必要时候,对于某些可能导致主从不一致的SQL语句,自动使用Row格式,记录数据变更
- 很好理解,这是为了平衡 性能和一致性
三大日志的核心作用
| 日志类型 | 核心作用 | 存储内容 | 所属组件 |
| redo log | 保证事务持久性(ACID 中的 D),崩溃恢复 | 物理日志:数据页的修改记录 | InnoDB 存储引擎 |
| undo log | 保证事务原子性(ACID 中的 A),支持回滚 | 逻辑日志:事务修改前的反向操作 | InnoDB 存储引擎 |
| binlog | 用于主从同步和时间点恢复(PITR) | 逻辑日志:SQL 语句或行修改记录 | MySQL 服务器层 |

5万+

被折叠的 条评论
为什么被折叠?



