Undo Log 是 InnoDB 实现事务回滚MVCC(多版本并发控制)的核心机制,记录事务修改前的旧版本数据。


一、核心作用与生命周期(Mermaid 图)

sequenceDiagram participant App as 应用 participant BP as Buffer Pool participant Undo as Undo Log participant Redo as Redo Log participant IB as ibd文件 App->>BP: UPDATE id=1 SET name='New' BP->>Undo: 记录旧值 name='Old'(Undo Record) BP->>BP: 修改数据页(脏页) BP->>Redo: 生成 Redo 日志 alt 事务回滚 BP->>Undo: 读取旧值 Undo->>BP: 回滚数据页 end alt 其他事务查询(MVCC) App->>BP: SELECT id=1 BP->>Undo: 根据事务ID判断可见性 Undo-->>App: 返回旧版本 name='Old' end alt 事务提交 & Purge BP-->>App: COMMIT OK Note over Undo,Purge: Purge线程定时清理<br>不再被任何事务需要的Undo Undo->>Purge: 标记删除 Purge->>Disk: 释放Undo空间 end

二、MySQL 8.0 的 Undo 革命

8.0 之前的痛点(5.7 及以下)

  • Undo 默认存储在 ibdata1 共享表空间

  • 无法收缩 :长事务导致 ibdata1 持续膨胀,只能重建实例

  • 性能瓶颈 :Undo 与数据混用表空间,I/O 竞争

8.0+ 的革新

  • Undo 独立表空间:自动创建 undo_001 ~ undo_010(10 个文件)

  • 在线 Truncate :Purge 后自动释放空间,无需停机

  • 可配置数量 :innodb_undo_tablespaces(静态参数,需重启)

  • 可配置路径 :innodb_undo_directory(可自定义到高速磁盘)

# MySQL 8.0 数据目录
/var/lib/mysql/
├── undo_001   # Undo 独立文件
├── undo_002
├── ...
├── ibdata1    # 不再存 Undo,只存数据字典
└── mydb/
    └── users.ibd

三、关键配置参数

1. innodb_undo_log_truncate(动态)

[mysqld]
innodb_undo_log_truncate = ON   # 默认 ON,自动收缩 Undo 文件
  • 作用 :当 Undo 文件超过 innodb_max_undo_log_size 时,Purge 后自动 Truncate

  • 风险 :关闭后 Undo 文件只增不减

2. innodb_max_undo_log_size(动态)

[mysqld]
innodb_max_undo_log_size = 1G   # 默认 1GB
  • 作用:Undo 文件达到此大小后,标记为可 Truncate

  • 调大场景:长事务频繁,避免频繁 Truncate 开销

3. innodb_undo_tablespaces(静态,重启生效)

[mysqld]
innodb_undo_tablespaces = 3     # 默认 2(8.0),推荐 3-5
  • 作用:Undo 文件数量,分散 I/O 压力

  • 规则:必须是 2~127 的整数,且 ≤ innodb_open_files

  • 8.0.14+ 默认 2 ,通常无需调整

4. innodb_undo_directory(静态)

[mysqld]
innodb_undo_directory = /ssd/undo  # 默认空(数据目录)
  • 作用:将 Undo 文件定向到独立高速磁盘(如 NVMe)

  • 场景:高并发写入,Undo I/O 与数据 I/O 分离

5. innodb_purge_threads(动态)

[mysqld]
innodb_purge_threads = 4        # 默认 4
  • 作用:Purge 线程数量,清理无用 Undo

  • 调大场景:大量 UPDATE/DELETE 后,Undo 堆积不释放


四、关键监控指标

1. Undo 空间占用

-- 查看 Undo 表空间大小
SELECT 
  NAME, 
  FILE_NAME, 
  ROUND(SIZE / 1024 / 1024, 2) AS size_mb,
  ROUND(MAX_SIZE / 1024 / 1024, 2) AS max_size_mb,
  STATE
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'innodb_undo%';

2. 长事务与 Undo 堆积

-- 查询持有 Undo 的活跃事务
SELECT 
  trx_id, trx_state, trx_started, 
  trx_mysql_thread_id, trx_rows_modified,
  TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
FROM information_schema.INNODB_TRX
WHERE trx_state = 'RUNNING'
  AND TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;  -- 超过 60 秒

3. Purge 延迟

-- 查看 Purge 进度
SHOW ENGINE INNODB STATUS\G
-- 输出中:
-- ------------
-- TRANSACTIONS
-- ------------
-- Purge done for trx's n:o < 12345 undo n:o < 6789

4. Undo 历史版本链长度

-- 查看最长版本链(MVCC 影响)
SELECT 
  TABLE_NAME, 
  MAX_UNDO_LOG_SIZE, 
  MIN_UNDO_LOG_SIZE
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'innodb_undo%';

五、最佳实践

1. 默认配置(90% 场景)

[mysqld]
# MySQL 8.0+ 默认已最优
innodb_undo_log_truncate = ON
innodb_max_undo_log_size = 1G
innodb_undo_tablespaces = 2      # 无需修改
innodb_purge_threads = 4         # 默认足够

无需手动调优,Undo 自动管理

2. 高并发写入场景

[mysqld]
innodb_undo_tablespaces = 5      # 增加到 5 个文件
innodb_max_undo_log_size = 2G    # 避免频繁 Truncate
innodb_purge_threads = 8         # 增加 Purge 线程
innodb_undo_directory = /nvme/undo  # 分离到 NVMe

3. 长事务管控(避免 Undo 堆积)

[mysqld]
# 限制事务时长(秒)
innodb_lock_wait_timeout = 50
wait_timeout = 600

# 监控长事务告警:duration > 300 秒

六、常见问题与排查

问题 1:Undo 文件持续增大不释放

-- 排查步骤
1. SHOW ENGINE INNODB STATUS\G 查看活跃事务
2. SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 10;
3. 找到 trx_mysql_thread_id,KILL 长事务
4. 等待 Purge 自动清理(或手动触发:无直接命令,靠 InnoDB 自动)

问题 2:磁盘空间告警,Undo 占满

# 紧急处理(业务低峰期)
SET GLOBAL innodb_undo_log_truncate = ON;  -- 确保开启
SET GLOBAL innodb_max_undo_log_size = 512M;  -- 降低阈值

# 查看清理进度
ls -lh /var/lib/mysql/undo_*
-- 文件大小会逐渐缩小

问题 3:Purge 太慢,Undo 堆积

-- 查看 Purge 线程状态
SHOW PROCESSLIST;
-- 找到 'purge' 线程,观察 Time 列

-- 调大线程数
SET GLOBAL innodb_purge_threads = 8;

七、长事务导致 Undo 暴增的实战案例

场景 :凌晨批处理 UPDATE 1000 万行,未提交,Undo 占用 200GB

-- 1. 查看 Undo 大小(此时 undo_001 已 150GB)
SELECT NAME, ROUND(SIZE/1024/1024/1024, 2) AS size_gb
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'innodb_undo%';

-- 2. 查看长事务
SELECT trx_id, trx_mysql_thread_id, trx_started, 
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
FROM information_schema.INNODB_TRX
ORDER BY duration_sec DESC;

-- 结果:trx_id 12345,duration_sec 7200(2 小时)

解决 :

-- 杀会话(需业务确认可回滚)
KILL 12345;  -- trx_mysql_thread_id

-- 等待 Purge 清理(约 10-30 分钟)
-- 监控:
SHOW STATUS LIKE 'Innodb_history_list_length';  -- 持续下降

预防 :

[mysqld]
# 限制单个事务修改行数
innodb_max_undo_log_size = 1G  # 达到后尽早触发 Truncate
# 应用层拆分大事务为 10 万行一批

八、Undo 与 Redo 的关系(PlantUML)


九、总结与配置速查

核心记忆点

  1. Undo 是 MVCC 和回滚 ,不是 Binlog 或 Redo

  2. MySQL 8.0+ Undo 自动管理 ,默认无需调优

  3. 长事务是唯一敌人 ,需监控 INNODB_TRX.duration_sec

  4. Undo 文件过大 → 杀长事务 → 等待 Purge

  5. innodb_undo_log_truncate = ON 必须开启

生产环境巡检 SQL

-- 一键巡检脚本
SELECT 
  'Undo 文件大小' AS check_item,
  CONCAT(ROUND(SUM(SIZE)/1024/1024/1024, 2), ' GB') AS value
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'innodb_undo%'

UNION ALL

SELECT 
  '最长事务时长(秒)',
  MAX(TIMESTAMPDIFF(SECOND, trx_started, NOW()))
FROM information_schema.INNODB_TRX

UNION ALL

SELECT 
  'Purge 线程数',
  @@innodb_purge_threads;

健康标准 :

  • Undo 总大小 < 10GB(或 < innodb_max_undo_log_size × 表空间数

  • 最长事务 < 300 秒

  • Purge 线程 = 4(默认)

一句话 :Undo Log 的问题,90% 是 长事务,10% 是配置。