MySQL Undo/Redo Log使用詳解
一、核心概念對比
| 特性 | Redo Log | Undo Log |
|---|---|---|
| 主要目的 | 保證事務(wù)的持久性 | 保證事務(wù)的原子性和MVCC |
| 寫入時(shí)機(jī) | 事務(wù)進(jìn)行中,數(shù)據(jù)修改前 | 事務(wù)進(jìn)行中,數(shù)據(jù)修改后 |
| 內(nèi)容 | 記錄物理修改操作 | 記錄邏輯修改前的數(shù)據(jù) |
| 存儲(chǔ)方式 | 順序?qū)懭?,循環(huán)覆蓋 | 隨機(jī)寫入,按需清理 |
| 生命周期 | 事務(wù)提交后,數(shù)據(jù)刷盤后可清理 | 事務(wù)提交后,但需保留至沒有事務(wù)依賴 |
| 恢復(fù)方向 | 前滾(redo) | 回滾(undo) |
| 文件 | ib_logfile0, ib_logfile1 | ibdata1或獨(dú)立表空間 |
二、Redo Log詳細(xì)原理
1.物理結(jié)構(gòu)
# 查看Redo Log配置 SHOW VARIABLES LIKE 'innodb_log%'; # 重要參數(shù): # innodb_log_file_size = 每個(gè)文件大?。J(rèn)48M-2G) # innodb_log_files_in_group = 文件數(shù)量(默認(rèn)2) # innodb_log_buffer_size = 緩沖區(qū)大小(默認(rèn)16M)
2.寫入流程(兩階段提交)
-- 示例:UPDATE users SET balance=200 WHERE id=1;
1. 內(nèi)存階段:
- 修改Buffer Pool中的數(shù)據(jù)頁
- 將修改記錄寫入Log Buffer
2. 準(zhǔn)備階段(Prepare):
- Log Buffer中的Redo記錄刷入Redo Log文件
- 此時(shí)Redo Log狀態(tài)為"prepare"
3. 提交階段(Commit):
- Binlog寫入完成
- Redo Log狀態(tài)改為"commit"
- 事務(wù)提交成功
3.Checkpoint機(jī)制
# Checkpoint過程示意
def checkpoint_process():
# 1. 找到最老的臟頁LSN(Log Sequence Number)
oldest_dirty_lsn = find_oldest_dirty_page()
# 2. 將這個(gè)LSN寫入Redo Log頭
write_checkpoint_to_redo(oldest_dirty_lsn)
# 3. 刷新臟頁到磁盤
flush_dirty_pages_to_disk()
# 4. 清理Redo Log空間
# 從checkpoint前的Redo Log可以安全覆蓋三、Undo Log詳細(xì)原理
1.存儲(chǔ)結(jié)構(gòu)
-- 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. 事務(wù)ID:DB_TRX_ID(最近修改的事務(wù)ID)
-- 3. 刪除標(biāo)記:DELETED_BIT2.版本鏈構(gòu)建
# 數(shù)據(jù)行的版本鏈?zhǔn)疽?br />當(dāng)前行 (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實(shí)現(xiàn)
-- 讀已提交(Read Committed)隔離級別下的可見性判斷 SELECT * FROM users WHERE id=1; -- InnoDB判斷流程: -- 1. 獲取當(dāng)前事務(wù)ID: current_trx_id -- 2. 獲取當(dāng)前活躍事務(wù)列表: active_trx_list -- 3. 從當(dāng)前行開始遍歷版本鏈: -- - 如果 trx_id < current_trx_id 且 trx_id不在活躍列表中 -- 且 trx_id <= up_limit_id(快照上限) -- - 則該版本對當(dāng)前事務(wù)可見 -- 4. 否則繼續(xù)查找更早版本
四、實(shí)際應(yīng)用場景
場景1:銀行轉(zhuǎn)賬事務(wù)
START TRANSACTION; -- 步驟1:A賬戶扣款 UPDATE accounts SET balance=balance-100 WHERE id=1; -- Undo Log記錄: (balance=原始值, trx_id=當(dāng)前事務(wù)) -- Redo Log記錄: 頁面修改信息 -- 步驟2:B賬戶入賬 UPDATE accounts SET balance=balance+100 WHERE id=2; -- Undo Log記錄: (balance=原始值, trx_id=當(dāng)前事務(wù)) -- Redo Log記錄: 頁面修改信息 COMMIT; -- Binlog寫入 → Redo Log狀態(tài)改為commit
恢復(fù)場景:
如果在COMMIT前崩潰:
Redo Log處于prepare狀態(tài)
重啟后根據(jù)Binlog決定是否提交
使用Undo Log回滾未提交的修改
如果在COMMIT后崩潰:
Redo Log為commit狀態(tài)
重啟后重做已提交的事務(wù)
場景2:長事務(wù)問題
-- 危險(xiǎn)的長時(shí)間查詢 START TRANSACTION; SELECT * FROM large_table WHERE ... FOR UPDATE; -- 執(zhí)行復(fù)雜業(yè)務(wù)邏輯(耗時(shí)10分鐘) -- ... COMMIT; -- 問題: -- 1. Undo Log無法清理,導(dǎo)致undo表空間膨脹 -- 2. 阻塞Purge線程 -- 3. 可能觸發(fā)“事務(wù)ID耗盡”問題
監(jiān)控腳本:
-- 監(jiān)控長時(shí)間運(yùn)行的事務(wù)
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;
-- 監(jiān)控Undo Log使用
SHOW ENGINE INNODB STATUS\G
-- 查看"TRANSACTIONS"部分場景3:批量數(shù)據(jù)處理優(yōu)化
-- 錯(cuò)誤的批量更新(產(chǎn)生大量Undo)
START TRANSACTION;
UPDATE large_table SET status=1 WHERE create_date < '2024-01-01';
-- 影響100萬行,產(chǎn)生大量Undo Log
COMMIT;
-- 優(yōu)化的批量更新
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; -- 每個(gè)批次提交,釋放Undo
SELECT SLEEP(0.1); -- 避免過度消耗資源
UNTIL ROW_COUNT() = 0 END REPEAT;
SET AUTOCOMMIT=1;
SET UNIQUE_CHECKS=1;
SET FOREIGN_KEY_CHECKS=1;五、性能優(yōu)化實(shí)戰(zhàn)
1.Redo Log優(yōu)化配置
# my.cnf配置示例 [mysqld] # Redo Log文件大小,建議設(shè)置為緩沖池的1/4到1/2 # 對于8G內(nèi)存,Buffer Pool通常6G,Redo Log設(shè)為2G innodb_log_file_size = 2G innodb_log_files_in_group = 4 # 總大小=2G*4=8G # Log Buffer大小,大事務(wù)可適當(dāng)調(diào)大 innodb_log_buffer_size = 64M # 刷盤策略,根據(jù)數(shù)據(jù)安全需求選擇 # 1-最安全,每次提交都刷盤(默認(rèn)) # 2-折中,每次提交寫OS緩存,每秒刷盤 # 0-性能最好,每秒刷盤,可能丟失1秒數(shù)據(jù) innodb_flush_log_at_trx_commit = 1
2.Undo表空間管理
-- MySQL 8.0+ 獨(dú)立Undo表空間
-- 查看Undo配置
SELECT * FROM information_schema.INNODB_TABLESPACES
WHERE SPACE_TYPE = 'Undo';
-- 監(jiān)控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%';
-- 設(shè)置Undo表空間自動(dòng)清理
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.高并發(fā)寫入優(yōu)化
-- 場景:秒殺活動(dòng),大量并發(fā)寫入 -- 問題:Redo Log成為瓶頸 -- 解決方案1:臨時(shí)調(diào)整刷盤策略(活動(dòng)期間) SET GLOBAL innodb_flush_log_at_trx_commit = 2; -- 活動(dòng)結(jié)束后恢復(fù)為1 -- 解決方案2:組提交優(yōu)化 -- MySQL已自動(dòng)支持,確保參數(shù)合理 SHOW VARIABLES LIKE 'binlog_group%'; SHOW VARIABLES LIKE 'innodb_flush_log_at_timeout'; -- 解決方案3:拆分熱點(diǎn)數(shù)據(jù) -- 將熱點(diǎn)賬戶分散到不同數(shù)據(jù)頁 UPDATE account_001 SET ... WHERE user_id=1; -- 分表
六、故障恢復(fù)實(shí)戰(zhàn)
場景:Redo Log損壞恢復(fù)
# 1. 檢查Redo Log狀態(tài) mysql> SHOW ENGINE INNODB STATUS\G # 2. 如果有備份,優(yōu)先使用備份恢復(fù) # 使用Percona XtraBackup或mysqldump備份 # 3. 強(qiáng)制恢復(fù)模式(謹(jǐn)慎使用) # 在my.cnf中添加 [mysqld] innodb_force_recovery = 1 # 1-6,數(shù)字越大越激進(jìn) # 4. 恢復(fù)步驟 # 4.1 停止MySQL sudo systemctl stop mysql # 4.2 備份原數(shù)據(jù)文件 cp -r /var/lib/mysql /var/lib/mysql_backup # 4.3 移除損壞的Redo Log mv /var/lib/mysql/ib_logfile* /tmp/ # 4.4 啟動(dòng)MySQL(會(huì)自動(dòng)創(chuàng)建新的Redo Log) sudo systemctl start mysql # 4.5 使用mysqlbinlog恢復(fù)未同步的數(shù)據(jù) mysqlbinlog mysql-bin.000001 | mysql -u root -p
七、監(jiān)控與維護(hù)腳本
-- 1. Redo Log監(jiān)控
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空間監(jiān)控
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. 長事務(wù)和Undo關(guān)聯(lián)查詢
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;八、最佳實(shí)踐總結(jié)
Redo Log優(yōu)化:
- 大小配置:總大小 = Buffer Pool的25%-50%
- IO優(yōu)化:使用SSD,單獨(dú)磁盤存放Redo Log
- 監(jiān)控告警:設(shè)置Redo Log切換頻率告警(> 20次/小時(shí)需擴(kuò)容)
Undo Log優(yōu)化:
- 避免長事務(wù):事務(wù)時(shí)間控制在5秒內(nèi)
- 定期清理:啟用
innodb_undo_log_truncate - 版本控制:及時(shí)提交只讀事務(wù),釋放快照
通用建議:
- 定期備份:Redo Log不是備份,需配合Binlog和物理備份
- 壓力測試:在高并發(fā)場景測試Redo/Undo配置
- 版本升級:MySQL 8.0在Undo管理上有顯著改進(jìn)
- 監(jiān)控完備:使用Prometheus+Granafa監(jiān)控Redo/Undo指標(biāo)
故障預(yù)案:
- 保持Redo Log在獨(dú)立磁盤
- 定期測試恢復(fù)流程
- 設(shè)置合理的
innodb_force_recovery預(yù)案 - 重要業(yè)務(wù)開啟雙1配置(
sync_binlog=1,innodb_flush_log_at_trx_commit=1)
通過深入理解Redo和Undo Log的工作原理,可以更好地設(shè)計(jì)數(shù)據(jù)庫架構(gòu)、優(yōu)化性能,并在故障時(shí)快速恢復(fù),確保業(yè)務(wù)的連續(xù)性和數(shù)據(jù)的安全性。
到此這篇關(guān)于MySQL Undo/Redo Log使用詳解的文章就介紹到這了,更多相關(guān)MySQL Undo/Redo Log內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL窗口函數(shù)OVER使用示例詳細(xì)講解
這篇文章主要介紹了MySQL窗口函數(shù)OVER()用法及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
MySQL讀寫分離的項(xiàng)目時(shí)間實(shí)踐
本文主要介紹了MySQL數(shù)據(jù)庫的讀寫分離技術(shù),包括一主一從和雙主雙從兩種架構(gòu),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
mysql千萬級數(shù)據(jù)量根據(jù)索引優(yōu)化查詢速度的實(shí)現(xiàn)
這篇文章主要介紹了mysql千萬級數(shù)據(jù)量根據(jù)索引優(yōu)化查詢速度的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
MySQL修改時(shí)間添加時(shí)間自動(dòng)更新的兩種方法
這篇文章主要介紹了MySQL修改時(shí)間添加時(shí)間自動(dòng)更新的兩種方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09

