一、核心概念对比
| 特性 | Redo Log | Undo Log |
|---|---|---|
| 主要目的 | 保证事务的持久性 | 保证事务的原子性和MVCC |
| 写入时机 | 事务进行中,数据修改前 | 事务进行中,数据修改后 |
| 内容 | 记录物理修改操作 | 记录逻辑修改前的数据 |
| 存储方式 | 顺序写入,循环覆盖 | 随机写入,按需清理 |
| 生命周期 | 事务提交后,数据刷盘后可清理 | 事务提交后,但需保留至没有事务依赖 |
| 恢复方向 | 前滚(redo) | 回滚(undo) |
| 文件 | ib_logfile0, ib_logfile1 | ibdata1或独立表空间 |
二、Redo Log详细原理
1. 物理结构
sql
# 查看Redo Log配置
SHOW VARIABLES LIKE 'innodb_log%';
# 重要参数:
# innodb_log_file_size = 每个文件大小(默认48M-2G)
# innodb_log_files_in_group = 文件数量(默认2)
# innodb_log_buffer_size = 缓冲区大小(默认16M)
2. 写入流程(两阶段提交)
sql
-- 示例:UPDATE users SET balance=200 WHERE id=1;
1. 内存阶段:
- 修改Buffer Pool中的数据页
- 将修改记录写入Log Buffer
2. 准备阶段(Prepare):
- Log Buffer中的Redo记录刷入Redo Log文件
- 此时Redo Log状态为"prepare"
3. 提交阶段(Commit):
- Binlog写入完成
- Redo Log状态改为"commit"
- 事务提交成功
3. Checkpoint机制
python
# Checkpoint过程示意
def checkpoint_process():
# 1. 找到最老的脏页LSN(Log Sequence Number)
oldest_dirty_lsn = find_oldest_dirty_page()
# 2. 将这个LSN写入Redo Log头
write_checkpoint_to_redo(oldest_dirty_lsn)
# 3. 刷新脏页到磁盘
flush_dirty_pages_to_disk()
# 4. 清理Redo Log空间
# 从checkpoint前的Redo Log可以安全覆盖
三、Undo Log详细原理
1. 存储结构
python
-- Undo Log段管理
CREATE TABLE t (
id INT PRIMARY KEY,
name VARCHAR(20),
balance DECIMAL(10,2)
) ENGINE=InnoDB;
-- Undo Log包含:
-- 1. 回滚指针:DB_ROLL_PTR(指向旧版本)
-- 2. 事务ID:DB_TRX_ID(最近修改的事务ID)
-- 3. 删除标记:DELETED_BIT
2. 版本链构建
python
# 数据行的版本链示意
当前行 (id=1, name='Alice', balance=100)
↓ roll_ptr
Undo Log 1: (balance=50, trx_id=100) ← UPDATE操作前的版本
↓ roll_ptr
Undo Log 2: (name='Bob', trx_id=80) ← 更早的UPDATE
↓ roll_ptr
NULL (初始版本)
3. MVCC实现
python
-- 读已提交(Read Committed)隔离级别下的可见性判断
SELECT * FROM users WHERE id=1;
-- InnoDB判断流程:
-- 1. 获取当前事务ID: current_trx_id
-- 2. 获取当前活跃事务列表: active_trx_list
-- 3. 从当前行开始遍历版本链:
-- - 如果 trx_id < current_trx_id 且 trx_id不在活跃列表中
-- 且 trx_id <= up_limit_id(快照上限)
-- - 则该版本对当前事务可见
-- 4. 否则继续查找更早版本
四、实际应用场景
场景1:银行转账事务
python
START TRANSACTION;
-- 步骤1:A账户扣款
UPDATE accounts SET balance=balance-100 WHERE id=1;
-- Undo Log记录: (balance=原始值, trx_id=当前事务)
-- Redo Log记录: 页面修改信息
-- 步骤2:B账户入账
UPDATE accounts SET balance=balance+100 WHERE id=2;
-- Undo Log记录: (balance=原始值, trx_id=当前事务)
-- Redo Log记录: 页面修改信息
COMMIT;
-- Binlog写入 → Redo Log状态改为commit
恢复场景:
-
如果在COMMIT前崩溃:
-
Redo Log处于prepare状态
-
重启后根据Binlog决定是否提交
-
使用Undo Log回滚未提交的修改
-
-
如果在COMMIT后崩溃:
-
Redo Log为commit状态
-
重启后重做已提交的事务
-
场景2:长事务问题
sql
-- 危险的长时间查询
START TRANSACTION;
SELECT * FROM large_table WHERE ... FOR UPDATE;
-- 执行复杂业务逻辑(耗时10分钟)
-- ...
COMMIT;
-- 问题:
-- 1. Undo Log无法清理,导致undo表空间膨胀
-- 2. 阻塞Purge线程
-- 3. 可能触发"事务ID耗尽"问题
监控脚本:
sql
-- 监控长时间运行的事务
SELECT
trx_id,
trx_started,
TIMEDIFF(NOW(), trx_started) AS duration,
trx_state,
trx_operation_state
FROM information_schema.INNODB_TRX
WHERE TIMEDIFF(NOW(), trx_started) > '00:05:00'
ORDER BY trx_started;
-- 监控Undo Log使用
SHOW ENGINE INNODB STATUS\G
-- 查看"TRANSACTIONS"部分
场景3:批量数据处理优化
sql
-- 错误的批量更新(产生大量Undo)
START TRANSACTION;
UPDATE large_table SET status=1 WHERE create_date < '2024-01-01';
-- 影响100万行,产生大量Undo Log
COMMIT;
-- 优化的批量更新
SET AUTOCOMMIT=0;
SET UNIQUE_CHECKS=0;
SET FOREIGN_KEY_CHECKS=0;
-- 分批次处理
DECLARE done INT DEFAULT FALSE;
DECLARE batch_size INT DEFAULT 1000;
DECLARE last_id INT DEFAULT 0;
REPEAT
START TRANSACTION;
UPDATE large_table
SET status=1
WHERE id > last_id
AND create_date < '2024-01-01'
LIMIT batch_size;
SET last_id = LAST_INSERT_ID();
COMMIT; -- 每个批次提交,释放Undo
SELECT SLEEP(0.1); -- 避免过度消耗资源
UNTIL ROW_COUNT() = 0 END REPEAT;
SET AUTOCOMMIT=1;
SET UNIQUE_CHECKS=1;
SET FOREIGN_KEY_CHECKS=1;
五、性能优化实战
1. Redo Log优化配置
bash
# my.cnf配置示例
[mysqld]
# Redo Log文件大小,建议设置为缓冲池的1/4到1/2
# 对于8G内存,Buffer Pool通常6G,Redo Log设为2G
innodb_log_file_size = 2G
innodb_log_files_in_group = 4 # 总大小=2G*4=8G
# Log Buffer大小,大事务可适当调大
innodb_log_buffer_size = 64M
# 刷盘策略,根据数据安全需求选择
# 1-最安全,每次提交都刷盘(默认)
# 2-折中,每次提交写OS缓存,每秒刷盘
# 0-性能最好,每秒刷盘,可能丢失1秒数据
innodb_flush_log_at_trx_commit = 1
2. Undo表空间管理
sql
-- MySQL 8.0+ 独立Undo表空间
-- 查看Undo配置
SELECT * FROM information_schema.INNODB_TABLESPACES
WHERE SPACE_TYPE = 'Undo';
-- 监控Undo空间使用
SELECT
tablespace_name,
file_size / 1024 / 1024 AS file_size_mb,
allocated_size / 1024 / 1024 AS allocated_mb
FROM information_schema.FILES
WHERE file_name LIKE '%undo%';
-- 设置Undo表空间自动清理
SET GLOBAL innodb_undo_log_truncate = ON;
SET GLOBAL innodb_max_undo_log_size = 1 * 1024 * 1024 * 1024; -- 1GB
SET GLOBAL innodb_purge_rseg_truncate_frequency = 128;
3. 高并发写入优化
sql
-- 场景:秒杀活动,大量并发写入
-- 问题:Redo Log成为瓶颈
-- 解决方案1:临时调整刷盘策略(活动期间)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
-- 活动结束后恢复为1
-- 解决方案2:组提交优化
-- MySQL已自动支持,确保参数合理
SHOW VARIABLES LIKE 'binlog_group%';
SHOW VARIABLES LIKE 'innodb_flush_log_at_timeout';
-- 解决方案3:拆分热点数据
-- 将热点账户分散到不同数据页
UPDATE account_001 SET ... WHERE user_id=1; -- 分表
六、故障恢复实战
场景:Redo Log损坏恢复
bash
# 1. 检查Redo Log状态
mysql> SHOW ENGINE INNODB STATUS\G
# 2. 如果有备份,优先使用备份恢复
# 使用Percona XtraBackup或mysqldump备份
# 3. 强制恢复模式(谨慎使用)
# 在my.cnf中添加
[mysqld]
innodb_force_recovery = 1 # 1-6,数字越大越激进
# 4. 恢复步骤
# 4.1 停止MySQL
sudo systemctl stop mysql
# 4.2 备份原数据文件
cp -r /var/lib/mysql /var/lib/mysql_backup
# 4.3 移除损坏的Redo Log
mv /var/lib/mysql/ib_logfile* /tmp/
# 4.4 启动MySQL(会自动创建新的Redo Log)
sudo systemctl start mysql
# 4.5 使用mysqlbinlog恢复未同步的数据
mysqlbinlog mysql-bin.000001 | mysql -u root -p
七、监控与维护脚本
sql
-- 1. Redo Log监控
SELECT
'Redo Log' AS metric,
CONCAT(ROUND(SUM(LENGTH)/1024/1024, 2), ' MB') AS current_size,
CONCAT(ROUND(variable_value/1024/1024, 2), ' MB') AS configured_size,
ROUND(SUM(LENGTH)*100/variable_value, 2) AS usage_percent
FROM information_schema.INNODB_BUFFER_PAGE
JOIN information_schema.GLOBAL_VARIABLES
ON variable_name = 'innodb_log_file_size'
WHERE PAGE_TYPE LIKE 'IBUF%'
GROUP BY variable_value;
-- 2. Undo空间监控
SELECT
FORMAT_BYTES(SUM(current_size)) AS active_undo_size,
FORMAT_BYTES(SUM(undo_size)) AS total_undo_size,
COUNT(*) AS undo_segments,
ROUND(SUM(current_size)*100/SUM(undo_size), 2) AS usage_pct
FROM information_schema.INNODB_METRICS
WHERE NAME LIKE '%undo%';
-- 3. 长事务和Undo关联查询
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
TIMEDIFF(NOW(), r.trx_started) AS wait_time,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
bl.lock_table AS locked_table
FROM information_schema.INNODB_LOCK_WAITS w
INNER JOIN information_schema.INNODB_TRX b
ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.INNODB_TRX r
ON r.trx_id = w.requesting_trx_id
INNER JOIN information_schema.INNODB_LOCKS bl
ON bl.lock_id = w.blocking_lock_id;
八、最佳实践总结
Redo Log优化:
-
大小配置:总大小 = Buffer Pool的25%-50%
-
IO优化:使用SSD,单独磁盘存放Redo Log
-
监控告警:设置Redo Log切换频率告警(> 20次/小时需扩容)
Undo Log优化:
-
避免长事务:事务时间控制在5秒内
-
定期清理 :启用
innodb_undo_log_truncate -
版本控制:及时提交只读事务,释放快照
通用建议:
-
定期备份:Redo Log不是备份,需配合Binlog和物理备份
-
压力测试:在高并发场景测试Redo/Undo配置
-
版本升级:MySQL 8.0在Undo管理上有显著改进
-
监控完备:使用Prometheus+Granafa监控Redo/Undo指标
故障预案:
-
保持Redo Log在独立磁盘
-
定期测试恢复流程
-
设置合理的
innodb_force_recovery预案 -
重要业务开启双1配置(
sync_binlog=1,innodb_flush_log_at_trx_commit=1)
通过深入理解Redo和Undo Log的工作原理,可以更好地设计数据库架构、优化性能,并在故障时快速恢复,确保业务的连续性和数据的安全性。