MySQL主從延遲根因定位排查大法
一、先量化延遲:別被假數(shù)據(jù)騙了
排查延遲的第一步,是拿到真實可信的延遲數(shù)值。
1.1 Seconds_Behind_Master 的局限
SHOW SLAVE STATUS\G -- 關(guān)注字段:Seconds_Behind_Master
這個值有一個致命缺陷:當(dāng) SQL 線程卡住時,它會停止更新,導(dǎo)致顯示值失真。在大事務(wù)或 DDL 阻塞場景下,它可能長時間靜止不動,但實際延遲仍在累積。
1.2 推薦:pt-heartbeat(精準(zhǔn)測量)
# 主庫:持續(xù)寫入心跳 pt-heartbeat --user=root --password=xxx --host=master \ --database=test --create-table --daemonize --update # 從庫:實時讀取延遲 pt-heartbeat --user=root --password=xxx --host=slave \ --database=test --monitor --master-server-id=1
pt-heartbeat 的優(yōu)勢:
- 跨時區(qū)安全,不依賴系統(tǒng)時鐘
- 精確到毫秒級
- SQL 線程卡住時依然能反映真實延遲
二、三層定位法:快速鎖定瓶頸層次
拿到延遲數(shù)值后,執(zhí)行以下語句,對照兩個關(guān)鍵位點判斷延遲來自哪一層:
SHOW SLAVE STATUS\G
| 對比位點 | 差距大說明 | 延遲層次 |
|---|---|---|
Master_Log_File vs Relay_Master_Log_File | binlog 沒傳過來 | 網(wǎng)絡(luò)層 / IO 線程 |
Relay_Log_File vs Exec_Master_Log_File | relay log 沒回放完 | SQL 線程 |
主庫寫入 → [網(wǎng)絡(luò)傳輸] → IO線程接收 → relay log → [SQL線程回放] → 從庫執(zhí)行
↑ 第一段延遲 ↑ 第二段延遲三、網(wǎng)絡(luò)層排查
3.1 診斷命令
# 測試帶寬(雙向) iperf3 -c master_ip -t 30 # 檢查 RTT 和丟包 ping master_ip -c 100 # 查看網(wǎng)卡實時流量 sar -n DEV 1 10
3.2 常見問題與對策
帶寬不足:binlog 產(chǎn)生速度 > 網(wǎng)絡(luò)傳輸速度
# my.cnf 從庫配置:開啟壓縮傳輸(CPU 換帶寬) [mysqld] slave_compressed_protocol = ON
網(wǎng)絡(luò)抖動導(dǎo)致重連慢:
# 縮短重連超時(默認(rèn) 60s 太長) slave_net_timeout = 30
跨機房場景:優(yōu)先申請專線或使用 VPN 隔離,避免公網(wǎng)延遲抖動。
四、IO 線程層排查
IO 線程慢的本質(zhì)是:主庫 binlog 產(chǎn)生速度 > IO 線程接收寫入速度。
4.1 檢查 binlog 產(chǎn)生速率
# 主庫:觀察 binlog 增長速度
mysqlbinlog --start-datetime="2024-01-01 10:00:00" \
--stop-datetime="2024-01-01 10:01:00" \
/var/lib/mysql/mysql-bin.000001 | wc -c
4.2 binlog_format 的影響
| format | event 大小 | 對延遲影響 |
|---|---|---|
| STATEMENT | ?。ㄖ挥?SQL) | 低帶寬,但有安全風(fēng)險 |
| ROW | 大(記錄行變更) | 帶寬消耗高,適合高一致性要求 |
| MIXED | 折中 | 推薦默認(rèn) |
binlog_format = ROW # 高一致性場景 binlog_row_image = MINIMAL # 減少 ROW 模式下的 event 大?。∕ySQL 5.6+)
4.3 主庫刷盤參數(shù)
# 主庫:高性能寫入(權(quán)衡持久性) sync_binlog = 0 # 0=OS 決定刷盤時機,性能最高 innodb_flush_log_at_trx_commit = 2 # 每秒刷盤,非每次事務(wù) # 主庫:高安全(金融場景) sync_binlog = 1 innodb_flush_log_at_trx_commit = 1
五、SQL 線程層排查(最常見根因)
這是高并發(fā)場景最普遍的瓶頸。主庫多線程并發(fā)寫入,從庫默認(rèn)單線程串行回放,必然追不上。
5.1 確認(rèn) SQL 線程是瓶頸
-- 確認(rèn) SQL 線程正在運行但回放慢 SHOW SLAVE STATUS\G -- Slave_SQL_Running: Yes -- Exec_Master_Log_Pos 長期落后 Relay_Log_Pos
5.2 開啟并行復(fù)制(核心解法)
MySQL 5.7+ 基于邏輯時鐘的并行復(fù)制(推薦):
[mysqld] # 從庫配置 slave_parallel_type = LOGICAL_CLOCK # 基于 binlog group commit 信息 slave_parallel_workers = 8 # 從 CPU 核數(shù) 50% 開始調(diào),逐步壓測 slave_preserve_commit_order = ON # 保證從庫事務(wù)提交順序與主庫一致 # 主庫需配合(提高 group commit 批量) binlog_group_commit_sync_delay = 100 # 微秒,等待更多事務(wù)進組 binlog_group_commit_sync_no_delay_count = 10
?? 注意:
slave_preserve_commit_order = ON必須開啟,否則從庫事務(wù)順序與主庫不一致,可能導(dǎo)致讀到臟數(shù)據(jù)。
5.3 驗證并行復(fù)制效果
-- 查看并行復(fù)制工作線程狀態(tài) SELECT * FROM performance_schema.replication_applier_status_by_worker\G -- 查看 worker 線程分配情況 SHOW STATUS LIKE 'Slave_worker%';
5.4 MySQL 8.0 的改進
MySQL 8.0 引入 Writeset 并行復(fù)制,不依賴 group commit,并行度更高:
binlog_transaction_dependency_tracking = WRITESET slave_parallel_type = LOGICAL_CLOCK slave_parallel_workers = 16
六、深層陷阱:大事務(wù) / 鎖競爭 / DDL / 磁盤 IO
6.1 大事務(wù)(最常被忽略的殺手)
大事務(wù)在主庫被多線程并發(fā)所掩蓋,到從庫單線程串行回放時會產(chǎn)生秒級甚至分鐘級卡頓。
定位大事務(wù):
# 找到 binlog 中的超大 event
mysqlbinlog --verbose /var/lib/mysql/mysql-bin.000001 \
| awk '/^# at/{pos=$3} /^### /{count++} /^COMMIT/{if(count>10000) print pos, count; count=0}'
# 或用 mysqlbinlog 直接統(tǒng)計
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001 \
| grep -E "^(# at|^### )" | awk '...'
業(yè)務(wù)層改造:
-- ? 危險:一次刪除 500 萬行 DELETE FROM orders WHERE created_at < '2023-01-01'; -- ? 安全:分批刪除,每批 1000 行 DELETE FROM orders WHERE created_at < '2023-01-01' LIMIT 1000; -- 循環(huán)執(zhí)行直到影響行數(shù)為 0
6.2 鎖競爭(從庫上的讀寫沖突)
從庫并非只讀——備份、統(tǒng)計查詢會產(chǎn)生鎖,與 SQL 線程的寫操作產(chǎn)生沖突。
-- 查看從庫當(dāng)前鎖等待 SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread FROM information_schema.INNODB_TRX b JOIN information_schema.INNODB_TRX r ON r.trx_wait_started IS NOT NULL;
對策:
- 從庫大查詢使用
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED - 備份使用
--single-transaction避免持鎖 - 將分析查詢遷移到專用的只讀從庫
6.3 DDL 阻塞
原生 DDL 在從庫執(zhí)行時會獨占 SQL 線程,期間所有回放暫停。
# 推薦:使用 gh-ost 進行在線表變更(不阻塞從庫) gh-ost \ --host=master \ --user=root --password=xxx \ --database=mydb \ --table=orders \ --alter="ADD INDEX idx_user_id(user_id)" \ --execute
或使用 Percona 的 pt-online-schema-change:
pt-online-schema-change \ --alter="ADD INDEX idx_user_id(user_id)" \ D=mydb,t=orders \ --execute
6.4 磁盤 IO 瓶頸
# 實時觀察磁盤 IO iostat -xm 1 10 # 找到 IO 最多的進程 iotop -o # 查看 MySQL 數(shù)據(jù)目錄所在磁盤 df -h /var/lib/mysql
關(guān)鍵參數(shù):
# 從庫可以適當(dāng)降低持久性換性能 innodb_flush_log_at_trx_commit = 2 # 從庫安全降級 innodb_flush_method = O_DIRECT innodb_io_capacity = 4000 # SSD 場景可調(diào)高至 8000-20000 innodb_io_capacity_max = 8000
七、關(guān)鍵參數(shù)速查表
| 參數(shù) | 推薦值 | 作用 | 適用位置 |
|---|---|---|---|
slave_parallel_workers | 4 ~ 16 | 并行回放線程數(shù) | 從庫 |
slave_parallel_type | LOGICAL_CLOCK | 并行復(fù)制策略 | 從庫 |
slave_preserve_commit_order | ON | 保證事務(wù)順序 | 從庫 |
sync_binlog | 1(安全)/ 0(性能) | 主庫 binlog 刷盤 | 主庫 |
innodb_flush_log_at_trx_commit | 1(主庫)/ 2(從庫) | redo log 刷盤 | 主 / 從 |
slave_net_timeout | 30 | 網(wǎng)絡(luò)超時重連 | 從庫 |
relay_log_recovery | ON | 從庫重啟自動修復(fù) | 從庫 |
slave_compressed_protocol | ON(跨機房) | 壓縮傳輸節(jié)省帶寬 | 從庫 |
binlog_row_image | MINIMAL | 減小 ROW 格式 event | 主庫 |
innodb_io_capacity | 4000 ~ 20000(SSD) | IO 調(diào)度上限 | 從庫 |
八、監(jiān)控告警體系搭建
8.1 Prometheus + mysqld_exporter
# prometheus.yml 抓取配置
scrape_configs:
- job_name: 'mysql_slave'
static_configs:
- targets: ['slave_host:9104']
核心監(jiān)控指標(biāo):
# 從庫延遲 mysql_slave_status_seconds_behind_master # IO 線程狀態(tài)(1=Running,0=異常) mysql_slave_status_slave_io_running # SQL 線程狀態(tài) mysql_slave_status_slave_sql_running # 并行復(fù)制 worker 等待 mysql_slave_status_slave_worker_count
8.2 Grafana 告警規(guī)則建議
# 告警閾值參考
- alert: MySQLReplicationLagWarning
expr: mysql_slave_status_seconds_behind_master > 10
for: 2m
annotations:
summary: "從庫延遲超過 10s,當(dāng)前值 {{ $value }}s"
- alert: MySQLReplicationLagCritical
expr: mysql_slave_status_seconds_behind_master > 30
for: 1m
annotations:
summary: "從庫延遲超過 30s(嚴(yán)重),當(dāng)前值 {{ $value }}s"
- alert: MySQLReplicationThreadDown
expr: mysql_slave_status_slave_sql_running == 0 or mysql_slave_status_slave_io_running == 0
for: 30s
annotations:
summary: "主從復(fù)制線程已停止"
8.3 pt-heartbeat 集成
# 主庫(systemd 守護進程)
pt-heartbeat --update --host=master --database=test \
--create-table --daemonize \
--pid=/var/run/pt-heartbeat.pid
# 從庫監(jiān)控(輸出毫秒級精度)
pt-heartbeat --monitor --host=slave --database=test \
--master-server-id=1 --frames=1m,5m,15m
九、建立持續(xù)治理 SOP
解決延遲不是一次性的,需要建立持續(xù)治理機制:
變更管控
- DDL 變更必須走審批流,使用
gh-ost或pt-osc - 批量寫入操作必須分批,單批不超過 1000 行
- 高峰期禁止大批量刪除/更新
容量規(guī)劃
- 主庫寫入 QPS 增長超過 20%,及時評估并行復(fù)制 worker 數(shù)量
- 監(jiān)控 binlog 產(chǎn)生速率,提前規(guī)劃磁盤和帶寬
定期演練
- 每季度模擬延遲場景,驗證告警鏈路是否暢通
- 記錄歷史延遲事件的根因和恢復(fù)時間(MTTR)
總結(jié)
主從延遲排查可以遵循以下優(yōu)先級:
1. 量化延遲(pt-heartbeat 優(yōu)于 Seconds_Behind_Master) 2. 用 SHOW SLAVE STATUS 定層(網(wǎng)絡(luò) / IO 線程 / SQL 線程) 3. SQL 線程慢 → 優(yōu)先開并行復(fù)制(80% 場景的解法) 4. 排查大事務(wù) → 業(yè)務(wù)改造分批寫 5. 檢查鎖競爭 → 減少從庫查詢干擾 6. DDL 變更 → 使用 gh-ost / pt-osc 7. 磁盤 IO → 升級 SSD + 調(diào)整 innodb_io_capacity
主從延遲沒有銀彈,需要結(jié)合業(yè)務(wù)寫入模式、硬件配置和 MySQL 版本綜合調(diào)優(yōu)。建議從并行復(fù)制入手,再逐步收斂到大事務(wù)治理和監(jiān)控體系完善。
到此這篇關(guān)于MySQL主從延遲根因定位排查大法的文章就介紹到這了,更多相關(guān)MySQL主從延遲排查內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決MySQL導(dǎo)入SQL時報錯1067–Invalid default value for
文章介紹了MySQL中報錯[ERR]1067-Invaliddefaultvaluefor‘a(chǎn)dd_date’的原因,以及如何通過修改my.ini文件禁用嚴(yán)格模式來解決這個問題2026-03-03
MYSQL必知必會讀書筆記第三章之顯示數(shù)據(jù)庫
MySQL是一種開放源代碼的關(guān)系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS),MySQL數(shù)據(jù)庫系統(tǒng)使用最常用的數(shù)據(jù)庫管理語言--結(jié)構(gòu)化查詢語言(SQL)進行數(shù)據(jù)庫管理。接下來通過本文給大家介紹MYSQL必知必會讀書筆記第三章之顯示數(shù)據(jù)庫,感興趣的朋友參考下吧2016-05-05
詳解mysql 使用left join添加where條件的問題分析
這篇文章主要介紹了詳解mysql 使用left join添加where條件的問題分析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
Mysql中LAST_INSERT_ID()的函數(shù)使用詳解
從名字可以看出,LAST_INSERT_ID即為最后插入的ID值,有了這個實用的函數(shù),我們可以實現(xiàn)很多問題,下面我們就來深入探討下。2015-03-03

