redo log、undo log、binlog:三种日志的区别

摘要:从一次"断电后数据丢失"和"数据重复写入"的诡异故障出发,深度剖析MySQL三种核心日志的作用与区别。通过图解WAL机制、redo log循环写入、undo log版本链、binlog的三种格式,揭秘两阶段提交如何保证redo log和binlog的一致性。用"转账"的完整案例贯穿始终,详解崩溃恢复的全流程,配合刷盘策略、参数调优、真实故障排查,让你彻底搞懂"为什么需要两份日志"、"断电后数据会怎样"等核心问题。


💥 翻车现场

周六凌晨3点,机房突然断电。

5分钟后,电力恢复,哈吉米紧急登录数据库检查数据。

-- 查询转账记录
SELECT * FROM account WHERE user_id IN (10086, 10087);

user_id | balance | update_time
--------|---------|-------------
10086   | 900     | 2024-10-07 02:58:32  -- 应该是800(转出200)
10087   | 1200    | 2024-10-07 02:58:32  -- 应该是1200(转入200)✅

-- 卧槽,user_id=10086的余额不对!
-- 明明转出了200,怎么还是900?

哈吉米:"断电前user_id=10086应该从1000扣减了100,变成900,这个对了。但后来又转出200,应该是700啊,怎么还是900?"

紧急查看binlog:

-- 查看binlog记录
SHOW BINLOG EVENTS IN 'mysql-bin.000123';

pos  | event_type | info
-----|------------|------
1000 | Query      | BEGIN
1050 | Update     | UPDATE account SET balance=900 WHERE user_id=10086  -- 第一次转账
1100 | Update     | UPDATE account SET balance=1100 WHERE user_id=10087
1150 | Xid        | COMMIT
1200 | Query      | BEGIN
1250 | Update     | UPDATE account SET balance=700 WHERE user_id=10086  -- 第二次转账
1300 | Update     | UPDATE account SET balance=1300 WHERE user_id=10087
-- 断电了,没有COMMIT!

哈吉米:"binlog记录了第二次转账,但没有COMMIT,所以第二次转账丢了?"

早上8点,南北绿豆和阿西噶阿西赶来了。

南北绿豆:"典型的日志不一致问题!你对redo log、undo log、binlog的理解不够深。"
阿西噶阿西:"而且我猜你的刷盘策略配置不对,断电时redo log还没来得及写磁盘。"
哈吉米:"redo log?undo log?我只知道binlog啊!"
南北绿豆:"来,今天给你讲透彻MySQL的三种日志!"


🤔 三种日志的作用对比

南北绿豆在白板上画了一个表格。

核心对比

特性redo logundo logbinlog
层级InnoDB引擎层InnoDB引擎层MySQL Server层
作用崩溃恢复(保证持久性D)事务回滚(保证原子性A)
MVCC(保证隔离性I)
主从复制
数据恢复
记录内容物理日志(数据页的变化)逻辑日志(相反操作)逻辑日志(SQL或行变化)
写入时机事务执行中事务执行中事务提交时
存储方式循环写入(固定大小)随机写入(按需分配)顺序追加(无限增长)
是否可删除可以(循环覆盖)可以(事务提交后)需要手动清理

哈吉米:"所以三种日志各管各的?"

阿西噶阿西:"对!redo log保证断电不丢数据,undo log保证事务能回滚,binlog保证主从一致。"

南北绿豆:"我们逐个讲解。"


🔥 redo log:崩溃恢复的保障

为什么需要redo log?

哈吉米:"为什么不直接把数据写磁盘,还要写redo log?"

南北绿豆:"因为随机IO太慢!"

问题场景

用户执行:UPDATE account SET balance = 900 WHERE user_id = 10086;

如果直接写磁盘:
1. 找到user_id=10086所在的数据页(可能在磁盘的任意位置)
2. 修改这一页(16KB)
3. 写回磁盘(随机IO,5-10ms)

问题:
- 每次UPDATE都要随机IO,性能极差
- 磁盘IO是性能瓶颈(1000个并发UPDATE → 1000次随机IO)

解决方案:WAL(Write-Ahead Logging)

WAL机制:
1. 修改数据时,先写内存(buffer pool)
2. 同时写redo log(顺序IO,0.1ms)
3. 返回"提交成功"
4. 后台异步把内存数据刷到磁盘(批量、顺序IO)

优点:
- 顺序IO比随机IO快100倍
- 批量刷盘,减少IO次数

阿西噶阿西:"所以redo log的核心是:用顺序IO代替随机IO,大幅提升性能。"


redo log的结构

南北绿豆:"redo log是循环写入的,像个环形缓冲区。"

结构图

redo log文件(固定大小,比如4个文件,每个1GB)

ib_logfile0 (1GB)  →  ib_logfile1 (1GB)  →  ib_logfile2 (1GB)  →  ib_logfile3 (1GB)
     ↑                                                                    ↓
     └─────────────────────────────────────────────────────────────────┘
                            循环写入

关键指针:
- write pos:当前写入位置
- checkpoint:已刷盘的位置

可用空间 = write pos 到 checkpoint 之间的空间

写入流程

1. 事务提交,写redo log(写到write pos位置)
2. write pos往前移动
3. 后台线程定期刷盘(把内存数据写到磁盘)
4. 刷盘完成,checkpoint往前移动
5. 如果write pos追上checkpoint,必须等待刷盘(阻塞写入)

图解

初始状态:
├────────────────────────────────┤
↑                                ↑
checkpoint                  write pos
(已刷盘)                  (当前写入位置)


写入事务后:
├────────────────────────────────┤
↑                                     ↑
checkpoint                       write pos
                                (写入了新数据)


后台刷盘后:
├────────────────────────────────┤
         ↑                            ↑
    checkpoint                   write pos
    (刷盘完成,checkpoint前移)


如果write pos追上checkpoint:
├────────────────────────────────┤
                            ↑
                    checkpoint = write posredo log满了,阻塞写入)

哈吉米:"所以redo log满了会阻塞写入?"

南北绿豆:"对!这时候必须等后台刷盘,释放空间。"


redo log的内容

阿西噶阿西:"redo log记录的是物理日志,也就是数据页的变化。"

示例

UPDATE account SET balance = 900 WHERE user_id = 10086;

redo log记录(简化表示):
页号: 123
偏移量: 456
旧值: balance = 1000
新值: balance = 900

意思是:在数据页123的偏移量456处,把balance从1000改成900

物理日志 vs 逻辑日志

类型记录内容示例
物理日志数据页的具体变化页123偏移456,把1000改成900
逻辑日志SQL语句或操作UPDATE account SET balance=900 WHERE user_id=10086

南北绿豆:"物理日志的好处是:恢复速度快,直接把数据写回去就行,不用重新执行SQL。"


redo log的刷盘策略

关键参数innodb_flush_log_at_trx_commit

阿西噶阿西:"这个参数决定了什么时候把redo log刷到磁盘,直接影响性能和数据安全。"

三种策略

-- 查看当前设置
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

-- 设置刷盘策略
SET GLOBAL innodb_flush_log_at_trx_commit = 1;

策略0:延迟写入(性能最好,最不安全)

流程:
1. 事务提交,redo log写到内存缓冲区(log buffer)
2. 返回"提交成功"
3. 后台线程每秒把log buffer刷到磁盘

风险:
- 如果1秒内MySQL崩溃,这1秒的事务全丢
- 如果1秒内OS崩溃,这1秒的事务全丢

图解

事务提交
   ↓
log buffer(内存)
   ↓(每秒一次)
OS缓存(内存)
   ↓
磁盘

适用场景:对数据安全要求不高,追求极致性能


策略1:每次提交都刷盘(默认,最安全)

流程:
1. 事务提交,redo log写到内存缓冲区(log buffer)
2. 调用fsync(),强制刷到磁盘
3. 返回"提交成功"

风险:
- MySQL崩溃:数据不丢(redo log在磁盘)
- OS崩溃:数据不丢(redo log在磁盘)
- 断电:数据不丢(redo log在磁盘)

代价:
- 每次事务都要磁盘IO,性能下降

图解

事务提交
   ↓
log buffer(内存)
   ↓(立即fsync)
OS缓存(内存)
   ↓(立即fsync)
磁盘 ✅

适用场景:⭐⭐⭐⭐⭐ 金融、支付等核心业务(推荐)


策略2:每次提交写OS缓存(折中)

流程:
1. 事务提交,redo log写到OS缓存
2. 返回"提交成功"
3. OS自己决定什么时候刷盘(通常1秒)

风险:
- MySQL崩溃:数据不丢(redo log在OS缓存)
- OS崩溃:这1秒的事务丢失
- 断电:这1秒的事务丢失

性能:比策略1好,比策略0差

图解

事务提交
   ↓
log buffer(内存)
   ↓(立即写入)
OS缓存(内存)✅
   ↓(OS决定)
磁盘

适用场景:折中方案,平衡性能和安全


三种策略对比

策略性能MySQL崩溃OS崩溃断电适用场景
0⭐⭐⭐⭐⭐❌ 可能丢1秒❌ 可能丢1秒❌ 可能丢1秒日志、临时表
1⭐⭐✅ 不丢✅ 不丢✅ 不丢⭐⭐⭐⭐⭐ 核心业务
2⭐⭐⭐⭐✅ 不丢❌ 可能丢1秒❌ 可能丢1秒非核心业务

南北绿豆:"生产环境推荐用策略1,虽然性能差点,但数据绝对安全。"

哈吉米:"我明白了!我之前用的是策略0,所以断电丢了数据!"


🔄 undo log:事务回滚与MVCC的基石

undo log的作用

阿西噶阿西:"undo log有两个核心作用。"

作用1️⃣:事务回滚

START TRANSACTION;

UPDATE account SET balance = 900 WHERE user_id = 10086;  -- 原来是1000
UPDATE account SET balance = 1100 WHERE user_id = 10087; -- 原来是1000

-- 如果这时候出错了,要回滚
ROLLBACK;

-- MySQL如何恢复?
-- 靠undo log记录的反向操作!

undo log记录

操作1UPDATE account SET balance = 900 WHERE user_id = 10086
undo log:UPDATE account SET balance = 1000 WHERE user_id = 10086  ← 反向操作

操作2UPDATE account SET balance = 1100 WHERE user_id = 10087
undo log:UPDATE account SET balance = 1000 WHERE user_id = 10087  ← 反向操作

回滚流程

ROLLBACK时:
1. 读取undo log
2. 执行反向操作
3. 恢复到事务开始前的状态

结果:
user_id=10086: balance = 1000(恢复了)
user_id=10087: balance = 1000(恢复了)

作用2️⃣:MVCC多版本并发控制

南北绿豆:"还记得我们讲MVCC时的版本链吗?那就是undo log构建的!"

回顾版本链

最新版本(在数据页中)
┌─────────────────────────────────┐
 user_id: 10086                  
 balance: 800                    
 DB_TRX_ID: 200                     事务200修改的
 DB_ROLL_PTR: ──────────────┐    
└─────────────────────────────│───┘
                              
                  undo log版本2
                  ┌─────────────────────────────────┐
                   user_id: 10086                  
                   balance: 900                    
                   DB_TRX_ID: 100                     事务100修改的
                   DB_ROLL_PTR: ──────────────┐    
                  └─────────────────────────────│───┘
                                                
                                    undo log版本1
                                    ┌─────────────────────────────────┐
                                     user_id: 10086                  
                                     balance: 1000                   
                                     DB_TRX_ID: 80                      最初的版本
                                     DB_ROLL_PTR: NULL               
                                    └─────────────────────────────────┘

南北绿豆:"通过undo log,不同事务可以看到不同版本的数据,这就是MVCC的核心!"


undo log的内容

undo log记录的是逻辑日志(相反操作):

操作undo log
INSERTDELETE(记录主键)
DELETEINSERT(记录完整数据)
UPDATEUPDATE(记录旧值)

示例

-- 操作
INSERT INTO account (user_id, balance) VALUES (10088, 1000);

-- undo log
DELETE FROM account WHERE user_id = 10088;


-- 操作
DELETE FROM account WHERE user_id = 10088;

-- undo log(记录完整数据,以便恢复)
INSERT INTO account (user_id, balance, create_time, ...) 
VALUES (10088, 1000, '2024-10-07 10:00:00', ...);


-- 操作
UPDATE account SET balance = 900 WHERE user_id = 10086;

-- undo log
UPDATE account SET balance = 1000 WHERE user_id = 10086;

undo log什么时候被清理?

哈吉米:"undo log会不会越来越多,占满磁盘?"

阿西噶阿西:"会清理,但有条件。"

清理时机

条件1:事务已提交
条件2:没有其他事务需要这个版本(MVCC)

满足两个条件后,后台线程(purge线程)会清理undo log

问题场景

如果有个长事务一直不提交:
START TRANSACTION;
SELECT * FROM account;  -- 生成Read View
-- 一直不COMMIT,持续1小时

结果:
-1小时内的所有undo log都不能清理(因为这个事务可能需要)
- undo log越积越多,占用大量空间
- 可能导致"undo log爆满"错误

查看undo log数量

-- 查看当前活跃的事务
SELECT * FROM information_schema.INNODB_TRX;

-- 查看undo log使用情况
SHOW ENGINE INNODB STATUS\G

History list length 12345  ← undo log条数,越大越危险

南北绿豆:"所以要避免长事务,及时提交或回滚!"


📝 binlog:主从复制与数据恢复

binlog的作用

南北绿豆:"binlog是Server层的日志,有两个核心作用。"

作用1️⃣:主从复制

主库写入binlog
   ↓
发送给从库
   ↓
从库执行binlog
   ↓
主从数据一致

详见上一篇文章:《主从复制与读写分离》


作用2️⃣:数据恢复

场景

今天中午12点,运维小王误删了整个订单表:
DROP TABLE order_info;

怎么恢复?
1. 恢复昨晚的全量备份(恢复到昨晚12点的状态)
2. 从昨晚12点到今天12点的binlog,重放一遍
3. 数据恢复到误删前的状态

恢复命令

# 1. 恢复全量备份
mysql < backup_2024-10-06.sql

# 2. 从binlog恢复增量数据
mysqlbinlog --start-datetime="2024-10-06 00:00:00" \
            --stop-datetime="2024-10-07 11:59:59" \
            mysql-bin.000123 | mysql

# 3. 数据恢复完成

binlog的三种格式

回顾

格式记录内容优点缺点
STATEMENT原始SQLbinlog小某些函数会导致主从不一致
ROW每行数据的变化一致性强binlog大
MIXED自动选择平衡判断可能不准

推荐ROW格式(MySQL 8.0默认)


binlog的写入时机

关键参数sync_binlog

-- 查看当前设置
SHOW VARIABLES LIKE 'sync_binlog';

-- 设置
SET GLOBAL sync_binlog = 1;

三种策略

策略0:OS自己决定(不安全)

流程:
1. 事务提交,binlog写到OS缓存
2. 返回"提交成功"
3. OS自己决定什么时候刷盘

风险:
- MySQL崩溃:不丢(OS缓存还在)
- OS崩溃:可能丢数据
- 断电:可能丢数据

策略1:每次提交都刷盘(最安全,推荐)

流程:
1. 事务提交,binlog写到OS缓存
2. 调用fsync(),强制刷到磁盘
3. 返回"提交成功"

风险:
- 任何情况都不丢数据 ✅

策略N:每N个事务刷一次

SET GLOBAL sync_binlog = 10;

流程:
1. 事务提交,binlog写到OS缓存
2. 每10个事务,调用fsync()刷盘

风险:
- 崩溃时可能丢失最多10个事务

sync_binlog对比

策略性能数据安全适用场景
0⭐⭐⭐⭐⭐❌ 可能丢不推荐
1⭐⭐✅ 不丢⭐⭐⭐⭐⭐ 核心业务(推荐)
N⭐⭐⭐⭐⚠️ 可能丢N个事务折中方案

南北绿豆:"生产环境推荐 sync_binlog=1,配合 innodb_flush_log_at_trx_commit=1,数据绝对安全!"


🔗 两阶段提交(2PC):redo log + binlog的协同

哈吉米:"为什么需要redo log和binlog两份日志?一份不行吗?"

阿西噶阿西:"因为历史原因职责分离。"

为什么有两份日志?

历史原因

MySQL最初没有InnoDB:
- 只有MyISAM引擎
- MyISAM不支持事务
- 所以只有binlog(用于主从复制)

后来引入InnoDB:
- InnoDB支持事务
- 需要redo log保证崩溃恢复
- 但binlog已经存在了,不能删(主从复制还要用)

结果:两份日志并存

职责分离

日志层级职责
redo logInnoDB引擎层崩溃恢复(保证事务持久性)
binlogServer层主从复制、数据恢复

南北绿豆:"虽然有两份日志,但必须保证两份日志的一致性,否则主从数据会不一致!"


两阶段提交的流程

问题场景

如果不协同,会出现什么问题?

场景1:先写redo log,后写binlog
1. 写redo log成功
2. 崩溃(binlog还没写)
3. 重启后,根据redo log恢复数据(主库有数据)
4. 从库没有binlog,无法同步(主库有,从库没有)❌

场景2:先写binlog,后写redo log
1. 写binlog成功
2. 崩溃(redo log还没写)
3. 重启后,没有redo log,数据没恢复(主库没数据)
4. 从库有binlog,同步了数据(主库没有,从库有)❌

解决方案:两阶段提交(2PC)

流程图

事务执行
   ↓
1. 【Prepare阶段】写redo log(标记为prepare状态)
   ↓
2. 写binlog
   ↓
3. 【Commit阶段】redo log改为commit状态
   ↓
事务提交完成

详细步骤

步骤1:执行SQL(写内存 + undo log)
UPDATE account SET balance = 900 WHERE user_id = 10086;

步骤2:Prepare阶段(写redo log)
- 写redo log
- 标记状态为"prepare"(未最终提交)

步骤3:写binlog
- 生成binlog event
- 写入binlog文件

步骤4:Commit阶段(修改redo log状态)
- redo log状态从"prepare"改为"commit"

步骤5:返回"提交成功"

图解

时间轴:
T1: 写redo log(状态=prepare)
T2: 写binlog
T3: redo log状态改为commit
T4: 返回"提交成功"

崩溃恢复的判断逻辑

阿西噶阿西:"两阶段提交的精妙之处在于:无论在哪个阶段崩溃,都能正确恢复!"

场景1:Prepare之前崩溃

崩溃时间:T0.5(还没写redo log)

恢复逻辑:
1. redo log没有记录 → 事务未执行
2. binlog也没有记录
3. 结论:事务丢失(正常,因为还没提交)

场景2:Prepare之后、写binlog之前崩溃

崩溃时间:T1.5redo log是prepare状态,binlog还没写)

恢复逻辑:
1. redo log有记录,状态=prepare
2. binlog没有记录
3. 结论:事务回滚(因为binlog没写,从库不会执行,主库也不能提交)

场景3:写binlog之后、Commit之前崩溃

崩溃时间:T2.5(binlog已写,redo log还是prepare状态)

恢复逻辑:
1. redo log有记录,状态=prepare
2. binlog有记录 ✅
3. 结论:事务提交(因为binlog已经写了,从库会执行,主库也要提交保持一致)

场景4:Commit之后崩溃

崩溃时间:T3.5redo log状态=commit,binlog已写)

恢复逻辑:
1. redo log有记录,状态=commit
2. binlog有记录
3. 结论:事务已提交(正常)

崩溃恢复的判断流程

重启后:
   ↓
读取redo log
   ↓
检查状态
   ↓
┌──────────────────────────────┐
│ 状态 = commit?              │
└──────┬───────────────────┬───┘
      YES                 NO
       ↓                   ↓
  事务已提交        状态 = prepare?
                          ↓
                  ┌───────┴───────┐
                 YES              NO
                  ↓                ↓
          检查binlog是否存在   事务未开始
                  ↓
          ┌───────┴───────┐
         YES              NO
          ↓                ↓
      提交事务         回滚事务

南北绿豆:"这就是两阶段提交的精妙之处:以binlog为准,保证主从一致!"

哈吉米:"卧槽,恍然大悟!所以redo log和binlog必须保持一致!"


🎯 完整案例:转账过程中断电

阿西噶阿西:"来个完整案例,梳理三种日志的协作。"

转账SQL

START TRANSACTION;

-- 转出方扣款
UPDATE account SET balance = balance - 200 WHERE user_id = 10086;
-- 原值:1000 → 新值:800

-- 转入方入账
UPDATE account SET balance = balance + 200 WHERE user_id = 10087;
-- 原值:1000 → 新值:1200

COMMIT;

完整执行流程

【事务开始】
T1: 执行第1UPDATE
    - 写内存(buffer pool):user_id=10086, balance=800
    - 写undo log:UPDATE account SET balance=1000 WHERE user_id=10086
    - 写redo log(prepare):页X偏移Y,1000800

T2: 执行第2UPDATE
    - 写内存(buffer pool):user_id=10087, balance=1200
    - 写undo log:UPDATE account SET balance=1000 WHERE user_id=10087
    - 写redo log(prepare):页X偏移Z,10001200COMMIT】
T3: Prepare阶段
    - redo log标记为prepare

T4: 写binlog
    - 生成binlog event
    - 写入binlog文件

T5: Commit阶段
    - redo log状态改为commit
    - 返回"提交成功"

T6: 后台异步刷盘
    - 把内存中的数据(800, 1200)写入磁盘

断电场景分析

场景1:T1.5断电(第1条UPDATE后)

已完成:
- 内存有balance=800
- undo log有反向操作
- redo log(prepare)有记录

未完成:
- 第2条UPDATE还没执行
- 没有COMMIT

恢复逻辑:
1. 读取redo log,状态=prepare
2. binlog没有完整事务记录
3. 回滚事务(根据undo log恢复)
4. 结果:user_id=10086 balance=1000(恢复了)

场景2:T4.5断电(binlog写完后)

已完成:
- 两条UPDATE都执行了
- redo log(prepare)都写了
- binlog写完了

未完成:
- redo log还没改成commit状态

恢复逻辑:
1. 读取redo log,状态=prepare
2. 检查binlog,发现有完整事务记录 ✅
3. 提交事务(把redo log改成commit)
4. 根据redo log恢复数据
5. 结果:user_id=10086 balance=800,user_id=10087 balance=1200(正确)

场景3:T6断电(后台刷盘时)

已完成:
- 事务已提交(redo log=commit)
- binlog已写入
- 但内存数据还没刷到磁盘

恢复逻辑:
1. 读取redo log,状态=commit
2. 根据redo log重做(把800, 1200写入磁盘)
3. 结果:数据正确

三种日志的协作总结

日志在转账中的作用
undo log记录旧值(1000, 1000),用于回滚
redo log记录新值(800, 1200),用于崩溃恢复
binlog记录完整事务,用于主从复制

核心原则

  1. 以binlog为准:binlog有记录,redo log就要提交(保证主从一致)
  2. 两阶段提交:prepare → binlog → commit(保证两份日志一致)
  3. 崩溃恢复:根据redo log + binlog的状态,决定提交还是回滚

📊 参数配置最佳实践

南北绿豆:"最后总结一下,生产环境该怎么配置。"

核心参数

-- 查看当前配置
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'sync_binlog';

-- 推荐配置(最安全)
SET GLOBAL innodb_flush_log_at_trx_commit = 1;  -- 每次提交都刷redo log
SET GLOBAL sync_binlog = 1;                     -- 每次提交都刷binlog

-- 折中配置(高并发场景)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;  -- 写OS缓存
SET GLOBAL sync_binlog = 10;                    -- 每10个事务刷一次binlog

配置对比

配置性能数据安全适用场景
innodb=1, binlog=1⭐⭐✅ 绝对安全⭐⭐⭐⭐⭐ 核心业务(推荐)
innodb=2, binlog=10⭐⭐⭐⭐⚠️ 可能丢少量数据高并发、非核心业务
innodb=0, binlog=0⭐⭐⭐⭐⭐❌ 可能丢很多数据临时表、日志表

其他重要参数

-- redo log大小(影响checkpoint频率)
SHOW VARIABLES LIKE 'innodb_log_file_size';
SET GLOBAL innodb_log_file_size = 1073741824;  -- 1GB

-- redo log文件数量
SHOW VARIABLES LIKE 'innodb_log_files_in_group';
-- 默认2个,可以设置为4个

-- binlog保留天数
SHOW VARIABLES LIKE 'expire_logs_days';
SET GLOBAL expire_logs_days = 7;  -- 保留7天

🎓 面试高频题

题目1:redo log和binlog的区别是什么?

答案

特性redo logbinlog
层级InnoDB引擎层MySQL Server层
作用崩溃恢复(保证持久性)主从复制、数据恢复
记录内容物理日志(数据页变化)逻辑日志(SQL或行变化)
写入时机事务执行中事务提交时
存储方式循环写入(固定大小)顺序追加(无限增长)

题目2:为什么需要两阶段提交?

答案

保证redo log和binlog的一致性,防止主从数据不一致。

流程

  1. Prepare:写redo log(状态=prepare)
  2. 写binlog
  3. Commit:redo log状态改为commit

崩溃恢复逻辑

  • 如果binlog有记录 → 提交事务(保证主从一致)
  • 如果binlog没记录 → 回滚事务

题目3:innodb_flush_log_at_trx_commit设置为多少最安全?

答案

设置为 1 最安全(每次提交都刷盘)。

行为数据安全
0写log buffer,每秒刷盘❌ 可能丢1秒数据
1立即刷盘✅ 绝对安全(推荐)
2写OS缓存,OS决定刷盘⚠️ OS崩溃可能丢1秒

生产环境推荐:innodb_flush_log_at_trx_commit=1 + sync_binlog=1


题目4:断电后,数据会丢吗?

答案

取决于配置:

配置断电是否丢数据
innodb_flush_log_at_trx_commit=1✅ 不丢(redo log在磁盘)
innodb_flush_log_at_trx_commit=0❌ 可能丢1秒
sync_binlog=1✅ 不丢(binlog在磁盘)
sync_binlog=0❌ 可能丢数据

最安全配置:两个参数都设置为1


题目5:undo log什么时候被清理?

答案

清理条件

  1. 事务已提交
  2. 没有其他事务需要这个版本(MVCC)

问题场景

  • 长事务不提交 → undo log无法清理 → 占用大量空间
  • 可能导致"undo log爆满"错误

解决方案

  • 避免长事务
  • 及时提交或回滚
  • 监控 History list length

🎉 结束语

晚上11点,三人终于把三种日志讲透了。

哈吉米:"原来redo log保证崩溃恢复,undo log保证事务回滚和MVCC,binlog保证主从复制!"

南北绿豆:"对!而且要记住两阶段提交,保证redo log和binlog的一致性。"

阿西噶阿西:"生产环境一定要配置 innodb_flush_log_at_trx_commit=1sync_binlog=1,虽然性能差点,但数据绝对安全!"

哈吉米:"我明天就改配置!下周咱们聊聊什么?"

南北绿豆:"死锁?我昨天又把测试库锁死了……"

哈吉米:"卧槽,又是你!"


三种日志记忆口诀

redo log保崩溃,WAL机制快如飞
undo log保回滚,MVCC版本靠它建
binlog保复制,主从一致不能少
两阶段提交妙,prepare先行binlog跟
commit最后到,崩溃恢复有依据


希望这篇文章能帮你彻底搞懂MySQL的三种日志!记住:理解日志的原理,才能理解MySQL的持久性和一致性保证

收藏+点赞,面试不慌张!💪