MySQL-12 常见问题定位
目录
MySQL 常见问题定位
目录
1. 问题排查通用流程
告警/反馈
↓
1. 确认影响范围(哪个服务?什么操作?多少用户?)
↓
2. 查看监控(QPS、延迟、连接数、CPU/内存/磁盘 I/O)
↓
3. 查看错误日志(/var/log/mysql/error.log)
↓
4. SHOW PROCESSLIST / SHOW ENGINE INNODB STATUS
↓
5. 针对具体问题深入排查
↓
6. 解决问题 + 记录根因
↓
7. 复盘改进(监控完善/代码优化/配置调整)
1.1 常用状态命令速查
-- 服务器整体状态
SHOW GLOBAL STATUS LIKE 'Threads%';
SHOW GLOBAL STATUS LIKE 'Connections';
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Com_%'; -- 各操作计数
-- 连接状态
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST; -- 显示完整 SQL
-- InnoDB 状态(最详细)
SHOW ENGINE INNODB STATUS\G
-- 表锁等待
SHOW STATUS LIKE 'Table_locks%';
-- InnoDB 行锁
SHOW STATUS LIKE 'Innodb_row_lock%';
2. 慢查询排查
2.1 发现慢查询
-- 方法1:慢查询日志(事后分析)
-- 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
-- 动态开启
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
-- 方法2:SHOW PROCESSLIST(实时)
-- 找 Time 列大的 Query
SELECT * FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep'
AND TIME > 5
ORDER BY TIME DESC;
-- 方法3:performance_schema(历史统计)
SELECT
DIGEST_TEXT,
COUNT_STAR AS exec_count,
ROUND(AVG_TIMER_WAIT / 1e12, 3) AS avg_sec,
ROUND(MAX_TIMER_WAIT / 1e12, 3) AS max_sec,
SUM_ROWS_EXAMINED AS total_rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'myapp'
ORDER BY avg_sec DESC
LIMIT 20;
2.2 分析慢 SQL
-- 1. EXPLAIN 分析执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 1 ORDER BY created_at DESC LIMIT 10;
-- 2. EXPLAIN ANALYZE(实际执行,MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT ...;
-- 3. 查看 Optimizer Trace(深度分析)
SET optimizer_trace = 'enabled=on';
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM information_schema.OPTIMIZER_TRACE\G
SET optimizer_trace = 'enabled=off';
-- 4. 查看 Profile(详细执行时间分解)
SET profiling = 1;
SELECT * FROM orders WHERE user_id = 1;
SHOW PROFILES; -- 显示所有查询
SHOW PROFILE FOR QUERY 1; -- 显示第1条查询的详细时间
-- 关注:Sending data 时间长 → 扫描行数多
-- Creating tmp table → GROUP BY/ORDER BY 用了临时表
-- Sorting result → filesort
2.3 常见慢查询原因和解决
-- 原因1:全表扫描(无索引)
-- EXPLAIN type = ALL
-- 解决:建索引
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 原因2:索引选择错误
-- EXPLAIN possible_keys 有多个,key 选了错误的
-- 解决:强制使用正确索引
SELECT * FROM orders FORCE INDEX (idx_user_id) WHERE user_id = 1;
-- 或更新统计信息
ANALYZE TABLE orders;
-- 原因3:深分页
-- LIMIT 1000000, 20 很慢
-- 解决:游标分页 + 延迟关联(见优化篇)
-- 原因4:filesort(无法利用索引排序)
-- Extra: Using filesort
-- 解决:调整索引,让 ORDER BY 列在索引中
-- 原因5:大量回表
-- type = ref,rows 很多,Rows_examined >> Rows_sent
-- 解决:覆盖索引
-- 原因6:数据量大的 IN 子查询
-- 解决:改为 JOIN 或分批查询
3. 连接数耗尽
3.1 症状
ERROR 1040 (HY000): Too many connections
3.2 排查
-- 当前连接情况
SELECT
USER,
HOST,
DB,
COMMAND,
TIME,
STATE,
INFO
FROM information_schema.PROCESSLIST
ORDER BY TIME DESC;
-- 按用户/主机/状态统计
SELECT USER, HOST, COMMAND, COUNT(*) AS cnt
FROM information_schema.PROCESSLIST
GROUP BY USER, HOST, COMMAND
ORDER BY cnt DESC;
-- 找长时间 Sleep 的连接(很可能是连接池未释放)
SELECT ID, USER, HOST, TIME
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 300
ORDER BY TIME DESC;
-- 查看最大连接数和当前连接数
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections'; -- 历史最大值
3.3 临时缓解
-- 临时提升 max_connections(需要 SUPER 权限)
-- 注意:需要保留 1 个超级用户的连接位置
SET GLOBAL max_connections = 1200;
-- Kill 掉长时间 Sleep 的连接
-- 先生成 KILL 语句
SELECT CONCAT('KILL ', ID, ';')
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 600;
-- 批量执行(在 bash 中)
mysql -u root -p -e "
SELECT CONCAT('KILL ', ID, ';')
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 600
" | mysql -u root -p
3.4 根本解决
1. 检查应用层连接池配置
- MaxOpenConns 是否合理
- MaxIdleConns 是否设置
- ConnMaxLifetime < MySQL wait_timeout
2. 检查是否有连接泄漏
- 事务未提交/回滚
- defer db.Close() 未执行
- 循环中未关闭 rows
3. 考虑 ProxySQL 连接池
- 前端:应用 → ProxySQL(少量连接)
- 后端:ProxySQL → MySQL(连接复用)
4. 业务高峰期的限流/熔断
3.5 Go 连接泄漏检查
// ❌ 常见连接泄漏:rows 未关闭
func leakExample(db *sql.DB) {
rows, err := db.Query("SELECT * FROM users")
if err != nil {
return
}
// 忘记 rows.Close()!循环结束后连接不会释放
for rows.Next() {
// ...
if someCondition {
return // 提前返回,rows 未关闭!
}
}
}
// ✅ 正确:使用 defer
func correctExample(db *sql.DB) {
rows, err := db.Query("SELECT * FROM users")
if err != nil {
return
}
defer rows.Close() // 确保一定关闭
for rows.Next() {
// ...
}
// 检查 rows.Err()
if err = rows.Err(); err != nil {
log.Error(err)
}
}
4. 死锁排查
4.1 查看死锁信息
-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS\G
-- 找 LATEST DETECTED DEADLOCK 部分
-- 开启死锁日志(将死锁信息写入错误日志)
SET GLOBAL innodb_print_all_deadlocks = ON;
4.2 死锁日志分析
LATEST DETECTED DEADLOCK
------------------------
2024-01-15 10:30:00 0x7f...
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 0 sec starting index read
MySQL thread id 100, OS thread handle ..., query id 200
UPDATE orders SET status=1 WHERE id=1 ← 事务1正在执行的SQL
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 10 n bits 72 index PRIMARY of table `myapp`.`orders`
trx id 12345 lock_mode X locks rec but not gap waiting ← 等待 orders.id=1 的 X 锁
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 0 sec starting index read
MySQL thread id 101, OS thread handle ..., query id 201
UPDATE orders SET status=2 WHERE id=2 ← 事务2正在执行的SQL
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS ... index PRIMARY of table `myapp`.`orders`
trx id 12346 lock_mode X locks rec but not gap ← 持有 orders.id=1 的 X 锁
*** WE ROLL BACK TRANSACTION (1) ← 回滚了事务1(代价小)
4.3 分析死锁原因
从死锁日志可以看出:
- 哪两个事务发生了死锁
- 它们分别持有什么锁
- 它们在等待什么锁
- 发生死锁的表和索引
常见死锁场景:
场景1:两个事务以不同顺序更新同两行
场景2:UPDATE 导致的范围锁和插入意向锁冲突
场景3:外键检查导致的锁链
场景4:事务在 SELECT FOR UPDATE 后 INSERT,和其他事务的 Gap Lock 冲突
5. 主从复制异常
5.1 复制停止排查
-- 查看从库状态
SHOW SLAVE STATUS\G
-- 关键字段:
-- Slave_IO_Running: Yes/No ← IO 线程状态
-- Slave_SQL_Running: Yes/No ← SQL 线程状态
-- Last_IO_Error: ← IO 线程错误
-- Last_SQL_Error: ← SQL 线程错误
-- Seconds_Behind_Master: ← 延迟秒数(NULL 表示复制停止)
-- Exec_Master_Log_Pos: ← 已执行到主库 Binlog 的位置
-- Relay_Log_Space: ← Relay Log 占用空间
5.2 常见复制错误处理
错误1:主键冲突(1062)
-- 场景:从库上已有数据,主库又插入了同样的主键
-- Last_SQL_Error: Error 'Duplicate entry '1' for key 'PRIMARY'' on query
-- 方法1:跳过这个错误(谨慎!可能导致数据不一致)
SET GLOBAL sql_slave_skip_counter = 1; -- 跳过 1 个事件
START SLAVE;
-- 方法2:GTID 模式下跳过(推荐)
-- 找到出错的 GTID(在 Last_SQL_Error 中)
STOP SLAVE;
SET GTID_NEXT = 'error_gtid_here';
BEGIN; COMMIT; -- 注入一个空事务,占用这个 GTID
SET GTID_NEXT = 'AUTOMATIC';
START SLAVE;
-- 方法3:重建从库(最干净,适合数据差异大时)
错误2:表不存在(1146)
-- 主库有某张表,从库没有
-- 解决:在从库手动创建表,或从主库同步
错误3:网络断开,Binlog 已删除
-- 主库的 Binlog 已经被清理,从库无法继续同步
-- 解决:重建从库(全量备份 + 重新配置)
5.3 主从延迟排查
-- 查看当前延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 延迟秒数
-- 找延迟原因
-- 1. 大事务(查看 Relay_Log_Space 增长速度)
-- 2. SQL 线程是否卡在某个慢 SQL
SHOW SLAVE STATUS\G -- 看 Exec_Master_Log_Pos 是否在推进
-- 3. 从库是否有慢查询
SHOW PROCESSLIST; -- 看 SQL Thread 正在执行什么
-- 4. 并行复制配置
SHOW VARIABLES LIKE 'slave_parallel%';
6. 磁盘空间问题
6.1 磁盘空间占用分析
# 查看 MySQL 数据目录大小
du -sh /var/lib/mysql/* | sort -rh | head -20
du -sh /var/lib/mysql/*.ibd | sort -rh | head -10
# 查看各目录大小
df -h # 查看磁盘挂载情况
-- 查看各数据库大小
SELECT
TABLE_SCHEMA AS db,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS total_mb,
ROUND(SUM(DATA_LENGTH) / 1024 / 1024, 2) AS data_mb,
ROUND(SUM(INDEX_LENGTH) / 1024 / 1024, 2) AS index_mb,
ROUND(SUM(DATA_FREE) / 1024 / 1024, 2) AS free_mb -- 碎片
FROM information_schema.TABLES
GROUP BY TABLE_SCHEMA
ORDER BY total_mb DESC;
-- 查看表大小
SELECT
TABLE_NAME,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS total_mb,
ROUND(DATA_FREE / 1024 / 1024, 2) AS free_mb,
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'myapp'
ORDER BY total_mb DESC
LIMIT 20;
6.2 释放磁盘空间
-- 方法1:删除旧数据(如日志表、归档数据)
DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 1000;
-- 分批删除!避免长事务
-- 方法2:OPTIMIZE TABLE(重建表,回收碎片)
OPTIMIZE TABLE orders;
-- 等价于 ALTER TABLE orders ENGINE=InnoDB;
-- 注意:会锁表,大表用 pt-online-schema-change 替代
-- 方法3:清理 Binlog
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 注意:确保从库已经消费完这些 Binlog 后再删!
-- 方法4:清理 Undo 表空间(MySQL 8.0)
-- 查看 Undo 空间大小
SELECT NAME, ROUND(FILE_SIZE/1024/1024, 2) AS size_mb
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE '%undo%';
-- 触发 Undo 空间收缩
SET GLOBAL innodb_undo_log_truncate = ON;
-- 等待 purge 线程清理后,自动收缩
6.3 磁盘满了怎么办(紧急处理)
# 1. 立即检查什么文件最大
du -sh /var/lib/mysql/* | sort -rh | head -10
# 2. 如果是 Binlog 太多
# 确认从库状态后删除
mysql -u root -p -e "SHOW SLAVE STATUS\G" --slave-host # 查从库
mysql -u root -p -e "PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY;"
# 3. 如果是临时文件 /tmp
ls -lh /tmp/
# 可以重启 MySQL 清理(谨慎!)
# 4. 如果是 ibtmp1(临时表空间)增长
# 重启 MySQL 会自动重置 ibtmp1
# 根因:有大查询产生了很多临时表(GROUP BY/DISTINCT 数据量大)
SHOW PROCESSLIST; # 找到并 KILL 掉问题查询
# 5. 临时腾出空间
# 压缩旧日志
gzip /var/log/mysql/slow.log.old
7. OOM 内存溢出
7.1 症状
MySQL 进程被 OOM Killer 杀死:
dmesg | grep -i "out of memory"
# 或
grep "Out of memory" /var/log/syslog
7.2 排查
-- 查看 MySQL 内存使用
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
-- performance_schema 内存使用详情
SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED / 1024 / 1024 AS current_mb
FROM performance_schema.memory_summary_global_by_event_name
ORDER BY current_mb DESC
LIMIT 20;
7.3 常见原因
1. innodb_buffer_pool_size 设置过大
→ 适当减小,留给 OS 足够内存
2. 大量并发连接 × 连接级别内存
→ 减少 max_connections
→ 减小 sort_buffer_size / join_buffer_size
3. 大查询产生大临时表
→ SHOW PROCESSLIST 找到并 KILL
→ 设置 tmp_table_size 和 max_heap_table_size
4. 内存碎片(长时间运行)
→ 定期重启(非紧急情况)
→ 使用 jemalloc 替换 glibc malloc
8. 数据误删恢复
8.1 快速止损
-- 发现误操作后,立刻:
-- 1. 记录当前时间点
SELECT NOW();
-- 2. 如果误操作的事务还未提交,立刻 ROLLBACK
ROLLBACK;
-- 3. 如果已提交,准备基于 Binlog 恢复
-- 千万不要继续写入数据(可能覆盖可恢复的数据)
8.2 Binlog 恢复步骤
# 场景:2024-01-15 14:30 误执行了 DELETE FROM orders WHERE user_id = 100
# 1. 找到最近的全量备份
# 假设:backup_20240115_000000.sql (当天凌晨备份)
# 2. 恢复全量备份到临时数据库
mysql -u root -p temp_restore < backup_20240115_000000.sql
# 3. 找到误操作的 Binlog 位置
mysqlbinlog mysql-bin.000001 \
--start-datetime='2024-01-15 00:00:00' \
--stop-datetime='2024-01-15 14:29:00' \ # 误操作前一分钟
> binlog_before_delete.sql
# 4. 应用 Binlog 到临时数据库
mysql -u root -p temp_restore < binlog_before_delete.sql
# 5. 从临时数据库中提取被删数据
mysql -u root -p temp_restore -e "
SELECT * FROM orders WHERE user_id = 100
" > recovered_orders.sql
# 6. 将数据导入生产数据库
mysql -u root -p production < recovered_orders.sql
8.3 延迟从库(最佳保护)
-- 配置延迟 1 小时的从库(延迟复制)
CHANGE MASTER TO MASTER_DELAY = 3600; -- 延迟 3600 秒
-- 误删后,立即暂停从库,让它停留在误操作前的时间点
STOP SLAVE SQL_THREAD;
-- 然后从从库恢复数据
-- 这是最快的恢复方式!
9. MySQL 崩溃恢复
9.1 查看崩溃原因
# 查看错误日志
tail -100 /var/log/mysql/error.log
# 查看系统日志
dmesg | tail -50
grep mysql /var/log/syslog | tail -50
# 常见崩溃原因:
# - InnoDB: Assertion failure(Bug 或数据损坏)
# - Signal 11(段错误,可能是 Bug 或内存问题)
# - OOM Killer 杀进程
# - 磁盘满
# - 文件权限问题
9.2 InnoDB 崩溃恢复
# 正常启动(InnoDB 自动重放 Redo Log 恢复)
mysqld --user=mysql
# 如果正常启动失败,尝试强制恢复模式
# innodb_force_recovery 从 1 到 6 逐步增大
# 数值越大,越能启动,但越多数据可能丢失
# 在 my.cnf 中设置
[mysqld]
innodb_force_recovery = 1 # 先从 1 开始
# 各级别含义:
# 1: 忽略损坏的 page(SRV_FORCE_IGNORE_CORRUPT)
# 2: 禁止主线程(不刷脏页)
# 3: 不执行事务 rollback
# 4: 不计算统计信息
# 5: 不检查 undo 日志一致性
# 6: 不 rollback forward(最危险)
# 能启动后,立刻导出数据
mysqldump -u root -p --all-databases > emergency_backup.sql
# 然后重建实例
9.3 表损坏修复
-- 检查表是否损坏
CHECK TABLE orders;
CHECK TABLE orders EXTENDED; -- 更全面
-- 修复(MyISAM)
REPAIR TABLE orders;
-- 修复(InnoDB,通过重建)
ALTER TABLE orders ENGINE = InnoDB;
-- 或
mysqldump -u root -p myapp orders > orders_backup.sql
DROP TABLE orders;
mysql -u root -p myapp < orders_backup.sql
10. 性能抖动排查
10.1 定期刷脏页导致抖动
症状:MySQL 每隔几秒出现响应慢高峰(周期性抖动)
原因:InnoDB 定期将 Buffer Pool 中的脏页刷到磁盘
如果脏页比例过高,会触发大量写 I/O
-- 查看脏页比例
SELECT
ROUND(VARIABLE_VALUE * 100 / @@innodb_buffer_pool_size, 2) AS dirty_pct
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_bytes_dirty';
-- 优化配置
innodb_io_capacity = 4000 -- 告知 InnoDB 磁盘 IOPS 能力(SSD 调大)
innodb_io_capacity_max = 8000 -- 紧急情况下的最大 IOPS
innodb_max_dirty_pages_pct = 75 -- 脏页比例超过 75% 开始积极刷盘
10.2 Redo Log 写满导致抖动
症状:写操作突然变慢(毫秒级变秒级)
原因:Redo Log 写满,必须等待 Checkpoint 推进(即刷脏页到磁盘)
-- 查看 Redo Log 使用率
SHOW ENGINE INNODB STATUS\G
-- 找 LOG 部分:
-- Log sequence number vs Last checkpoint at 的差值
-- 差值 / innodb_log_file_size × innodb_log_files_in_group
-- 解决:增大 Redo Log 大小
innodb_redo_log_capacity = 4G -- MySQL 8.0.30+
-- 或
innodb_log_file_size = 1G
innodb_log_files_in_group = 2
10.3 MDL 锁等待导致抖动
-- 症状:某段时间所有查询都慢,之后恢复正常
-- 排查:
SELECT * FROM sys.schema_table_lock_waits;
-- 找到持有 MDL 写锁的进程(ALTER TABLE)
SELECT * FROM performance_schema.metadata_locks
WHERE LOCK_TYPE = 'EXCLUSIVE';
-- Kill 掉
KILL QUERY {thread_id};
11. 常用诊断 SQL
-- ==========================================
-- 系统概览
-- ==========================================
-- QPS(每秒查询数)
SELECT ROUND(
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Queries') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Uptime'),
2
) AS QPS;
-- TPS(每秒事务数)
SELECT ROUND(
((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Com_commit') +
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Com_rollback')) /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Uptime'),
2
) AS TPS;
-- Buffer Pool 命中率(应 > 99%)
SELECT
ROUND(
(1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests')
) * 100, 4
) AS buffer_pool_hit_rate_pct;
-- ==========================================
-- 锁相关
-- ==========================================
-- 当前等待锁的事务
SELECT
r.trx_id AS waiting_trx,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx,
b.trx_query AS blocking_query,
b.trx_mysql_thread_id AS blocking_thread
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;
-- 长事务(超过 60 秒)
SELECT
trx_id,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec,
trx_mysql_thread_id AS thread_id,
trx_rows_modified,
LEFT(trx_query, 100) AS query
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60
ORDER BY trx_started;
-- ==========================================
-- 表空间
-- ==========================================
-- 表碎片率高的表(Data_free / (Data_length + Index_length))
SELECT
TABLE_NAME,
ROUND(DATA_LENGTH / 1024 / 1024, 2) AS data_mb,
ROUND(INDEX_LENGTH / 1024 / 1024, 2) AS index_mb,
ROUND(DATA_FREE / 1024 / 1024, 2) AS free_mb,
ROUND(DATA_FREE / (DATA_LENGTH + INDEX_LENGTH) * 100, 2) AS frag_pct
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'myapp'
AND DATA_FREE > 0
ORDER BY frag_pct DESC
LIMIT 20;
-- ==========================================
-- 复制
-- ==========================================
-- 主从延迟告警(延迟超过 30 秒)
-- 在从库执行
SELECT
IF(TIMESTAMPDIFF(SECOND, MIN(trx_started), NOW()) > 30,
'ALERT: Replication lag detected',
'OK') AS status
FROM information_schema.INNODB_TRX;
-- 更准确的方式是看 SHOW SLAVE STATUS 的 Seconds_Behind_Master
小结
故障排查的黄金原则:
1. 先止损,再排查
- 发现问题先评估影响,必要时限流/降级/切流量
2. 保留现场
- 记录时间点、错误信息、PROCESSLIST 快照
- 不要急于重启(可能丢失现场信息)
3. 分层排查
- 应用层(代码/SQL 是否有问题)
- MySQL 层(连接/锁/慢查询)
- OS 层(CPU/内存/I/O/网络)
- 硬件层(磁盘/内存)
4. 记录复盘
- 每次故障都写故障报告
- 改进监控告警,让类似问题早发现
xingliuhua