一、核心概念对比
| 特性 | redo log | undo log |
|---|---|---|
| 主要目的 | 保证事务的持久性 | 保证事务的原子性和mvcc |
| 写入时机 | 事务进行中,数据修改前 | 事务进行中,数据修改后 |
| 内容 | 记录物理修改操作 | 记录逻辑修改前的数据 |
| 存储方式 | 顺序写入,循环覆盖 | 随机写入,按需清理 |
| 生命周期 | 事务提交后,数据刷盘后可清理 | 事务提交后,但需保留至没有事务依赖 |
| 恢复方向 | 前滚(redo) | 回滚(undo) |
| 文件 | ib_logfile0, ib_logfile1 | ibdata1或独立表空间 |
二、redo log详细原理
1.物理结构
# 查看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.写入流程(两阶段提交)
-- 示例: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机制
# 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.存储结构
-- 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_bit2.版本链构建
# 数据行的版本链示意
当前行 (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实现
-- 读已提交(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:银行转账事务
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:长事务问题
-- 危险的长时间查询 start transaction; select * from large_table where ... for update; -- 执行复杂业务逻辑(耗时10分钟) -- ... commit; -- 问题: -- 1. undo log无法清理,导致undo表空间膨胀 -- 2. 阻塞purge线程 -- 3. 可能触发“事务id耗尽”问题
监控脚本:
-- 监控长时间运行的事务
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:批量数据处理优化
-- 错误的批量更新(产生大量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优化配置
# 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表空间管理
-- 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.高并发写入优化
-- 场景:秒杀活动,大量并发写入 -- 问题: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损坏恢复
# 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
七、监控与维护脚本
-- 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的工作原理,可以更好地设计数据库架构、优化性能,并在故障时快速恢复,确保业务的连续性和数据的安全性。
到此这篇关于mysql undo/redo log使用详解的文章就介绍到这了,更多相关mysql undo/redo log内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论