主题
MySQL 监控指标与告警
一句话:MySQL 的雪崩很少始于 CPU 打满,而是从连接排队、缓冲池命中率、Undo 回收和复制延迟这四个慢变量开始;看斜率和组合,比看单点峰值更早发现问题。适用版本:MySQL 8.0 / 8.4(InnoDB),阈值都是起点,必须按业务基线校准。
为什么需要
监控要回答的不是「现在有没有事」,而是「再过十分钟会不会有事」。QPS、P99、错误率是结果指标,等它们变坏时用户已经受影响;连接排队、缓冲池命中率、History list、复制延迟是先兆指标,给的是处置窗口。MySQL 的指标还分瞬时值和累计值:Threads_connected、Innodb_history_list_length 是瞬时值;Innodb_buffer_pool_reads、Innodb_row_lock_waits 是重启清零的累计值。累计值必须做单位时间增量,拿绝对值设阈值一定误报。下面按「指标反映什么 → 为什么是先兆 → 恶化后怎么雪崩」分层。
核心机制
连接层:先看排队,再看总量
| 指标 | 反映什么 | 先兆含义 |
|---|---|---|
Threads_connected | 当前连接数,含空闲连接 | 持续逼近 max_connections(8.0 默认 151)说明池在堆积 |
Threads_running | 非 Sleep 状态的线程数 | 持续高于 CPU 核数或基线两倍,说明查询在排队,不等于 CPU 忙 |
Max_used_connections | 启动以来的连接高水位 | 等于 max_connections 说明已经撞过上限 |
Aborted_connects | 握手/认证失败的连接次数 | 突增常见于密码错误、DNS、网络抖动或连接池配置错误 |
Connection_errors_max_connections | 因连接上限被拒绝的次数 | 一旦非零,说明入口已经饱和,应用正在收到连接错误 |
Threads_connected 高而 Threads_running 低,通常是空闲连接或连接池泄漏;两个都高才是真实压力。雪崩路径是:查询变慢 → 连接持有时间变长 → 连接数逼近上限 → 新连接被拒绝或超时 → 应用重试 → 请求量再放大。
InnoDB:缓冲池、刷脏与 Undo
| 指标 | 反映什么 | 先兆含义 |
|---|---|---|
命中率 1 - ΔInnodb_buffer_pool_reads / ΔInnodb_buffer_pool_read_requests | 逻辑读中有多少落到磁盘 | 长窗口持续下降,说明工作集超出缓冲池,物理读开始放大 |
Innodb_buffer_pool_pages_free | 空闲页数 | 预热后接近 0 是正常的;配合 Innodb_buffer_pool_wait_free > 0 才说明没有页可淘汰 |
Innodb_data_pending_fsyncs | 待完成的 fsync 数 | 持续非零说明刷盘排队,磁盘已经跟不上 |
Innodb_log_waits | 等待 log buffer 空间的次数 | 增量持续大于 0,说明提交突发或 log buffer 偏小 |
Innodb_history_list_length | 待 Purge 的 Undo 版本数 | 持续上涨说明有老 ReadView 或 Purge 被 IO 拖住 |
Innodb_row_lock_waits / Innodb_row_lock_time | 行锁等待次数 / 累计毫秒 | 增量上升说明热点行冲突加剧;Innodb_row_lock_current_waits 是当前等待数 |
四条因果链要连起来看:长事务持有 ReadView → Undo 版本不能回收 → History list 增长 → Undo 表空间和 ibdata 膨胀 → 磁盘压力反过来拖慢 Purge;缓冲池命中率下降 → 物理读放大 → 查询 P99 上升 → 连接堆积;行锁等待 → 事务变长 → Undo 更多;长事务不提交 → DDL 拿不到 MDL 排他锁 → 后续 DML 排在 DDL 后面,表现为假死。
复制:延迟不只是秒数
| 指标 | 反映什么 | 先兆含义 |
|---|---|---|
Seconds_Behind_Source(8.0.22+,旧名 Seconds_Behind_Master) | 回放时间落后源库的秒数 | 为 NULL 说明复制线程没跑;为 0 也可能是 IO 线程停了但 SQL 线程恰好追平 |
Relay_Log_Space | Relay log 占用字节数 | 持续增长说明 SQL 线程回放慢于 IO 线程接收 |
Replica_IO_Running / Replica_SQL_Running | 两个复制线程状态 | 任一不是 Yes,延迟秒数已经失去意义,应立即告警 |
GTID 差集 Retrieved_Gtid_Set 与 Executed_Gtid_Set | 已收到但未执行的事务 | 差集扩大就是真实回放缺口;再看 replication_applier_status_by_worker 的 LAST_ERROR_MESSAGE |
复制延迟会反噬主库:从库读到旧数据 → 业务重试或补偿 → 更多写请求打回主库 → 主库更忙、延迟更大。Relay_Log_Space 同时会推高从库磁盘。
SQL 层与 OS 层:放大器的两端
- 慢查询日志:
slow_query_log8.0 默认 OFF,long_query_time默认 10 秒,log_queries_not_using_indexes默认 OFF;直接打开未走索引记录可能刷爆日志,先配min_examined_row_limit。 performance_schema.events_statements_summary_by_digest:看SUM_TIMER_WAIT(皮秒)、SUM_ROWS_EXAMINED / SUM_ROWS_SENT、SUM_NO_INDEX_USED(全表扫描/未用索引)、SUM_CREATED_TMP_DISK_TABLES;扫描行数远大于返回行数说明索引或写法有问题,临时表落盘说明排序/分组超出内存。- OS:
top/vmstat 1看 CPU、可用内存和si/so(swap 有进出就是危险信号);iostat -x 1看await、%util、r/s、w/s(IOPS)。NVMe 上%util可能因并行度高而失真,要和 IOPS、延迟一起看。
雪崩链路
这张图有两条正反馈:一条从长事务出发,经 Undo 和磁盘回到 IO;另一条从查询变慢出发,经连接堆积和应用重试回到长事务。处置时打断任意一条都比「加机器」快;两条都不断,扩容只会把雪崩推迟几分钟。
最小上手
sql
-- 连接、缓冲池、Undo、行锁:先看瞬时值和累计值
SHOW GLOBAL STATUS WHERE Variable_name IN (
'Threads_connected','Threads_running','Max_used_connections','Aborted_connects',
'Connection_errors_max_connections','Innodb_buffer_pool_reads','Innodb_buffer_pool_read_requests',
'Innodb_buffer_pool_pages_free','Innodb_buffer_pool_wait_free','Innodb_data_pending_fsyncs',
'Innodb_log_waits','Innodb_history_list_length','Innodb_row_lock_waits',
'Innodb_row_lock_time','Innodb_row_lock_current_waits'
);
SHOW ENGINE INNODB STATUS\G -- 看 TRANSACTIONS 段的 History list length
SHOW REPLICA STATUS\G -- 8.0.22+;旧版本用 SHOW SLAVE STATUS
-- 谁在跑、谁在等
SELECT thd_id, conn_id, user, db, command, time, state, rows_examined,
rows_sent, tmp_disk_tables, full_scans, LEFT(current_statement, 80) AS stmt
FROM sys.session WHERE command <> 'Sleep' ORDER BY time DESC LIMIT 20;
SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec,
SUM_ROWS_EXAMINED, SUM_ROWS_SENT, SUM_NO_INDEX_USED, SUM_CREATED_TMP_DISK_TABLES
FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;promql
# 缓冲池命中率(5 分钟)
100 * (1 - rate(mysql_global_status_innodb_buffer_pool_reads[5m]) / clamp_min(rate(mysql_global_status_innodb_buffer_pool_read_requests[5m]), 1))
# 连接使用率
100 * mysql_global_status_threads_connected / mysql_global_variables_max_connections复制延迟的 exporter 指标名随 mysqld_exporter 版本变化(旧版常见 mysql_slave_status_seconds_behind_master),以目标实例 /metrics 的实际输出为准。
生产动作
| 告警 | 起点阈值 | 说明 |
|---|---|---|
| 连接使用率与错误 | Threads_connected / max_connections > 80% 持续 5 分钟,或连接错误增量突增 | 先查泄漏、慢 SQL、认证和网络,不要只调大上限 |
| 运行线程 | Threads_running 高于 CPU 核数或基线两倍,持续 5 分钟 | 结合 Threads_connected 判断是排队还是空连接 |
| 复制延迟 | Seconds_Behind_Source > 30s 或持续增长 | 线程不是 Yes 时立即告警,不看秒数 |
| History list | > 100 万 且持续上涨 | 起点值,先查长事务和 Purge 线程 |
| 行锁与长事务 | 等待增量超基线,或事务打开 > 60s | 用 sys.innodb_lock_waits 找阻塞源 |
| 磁盘与 IO | 使用率 > 80%,或 await 超基线两倍且 %util > 80% | 同时看 binlog、undo、relay log 的增长 |
| 慢查询/全表扫描 | digest 增量相对基线突增 | 看 SUM_ROWS_EXAMINED 和 SUM_NO_INDEX_USED |
| 命中率与刷盘 | 长窗口低于 99%(OLTP 起点)且下降,或 Innodb_data_pending_fsyncs 持续非零 | 大范围扫描也会拉低命中率;刷盘排队先看磁盘,再考虑 log buffer |
处置顺序默认是「先限流止血,再定位,最后扩容」:
- 保护入口:应用侧限流、关闭重试风暴、降级非核心查询。连接池满先查泄漏,直接调大
max_connections往往把线程调度和内存压力放大。 - 打断链路:确认后 kill 长事务或阻塞源,释放 MDL;不要批量 kill 所有等待者,否则可能反复重试。
- 定位:用 digest 和 OS 指标判断是读放大、锁、复制还是磁盘;不要凭单个峰值下结论。
- 扩容/改配置并复盘:读瓶颈加只读副本或缓存,写瓶颈升配或分片;
innodb_buffer_pool_size调整通常要重启,救不了当前故障。恢复后把触发条件和处置动作写回 runbook。
边界
- 指标名、默认值、复制术语随版本变化:8.0.22+ 用
SHOW REPLICA STATUS、Seconds_Behind_Source;8.4 继续沿用 source/replica 术语,旧语法是否保留以 8.4 手册为准。 - 累计值重启清零,必须用
rate/delta;跨实例比较要用比率,不要比绝对值。 - 命中率不是越高越好,也不是低就一定故障;全表扫描、预热期、小工作集都会影响它。
Seconds_Behind_Source是近似值,时钟偏差、SQL 线程空闲、大事务都会让它失真;关键业务用 GTID 差集或心跳表补充。监控也替代不了业务指标,用户侧 P99 和错误率才是最终判据。
优缺点与扬长避短
优点是单主 OLTP 的写入路径清晰,InnoDB 的 B+ 树索引、事务和崩溃恢复成熟;异步/半同步/Group Replication 三种复制形态都有,备份、代理、云托管和 exporter 生态完整。代价是写扩展依赖分库分表,大事务和 DDL 成本高(Undo 与 MDL),分析型查询和向量检索弱(8.0/8.4 无原生向量索引,需要外部系统)。
| 场景 | 判断 | 原因 |
|---|---|---|
| 单库 OLTP、强事务、读多写少 | 适用 | 点查、事务、读写分离路径成熟 |
| 写入需要横向扩展 | 单实例不适用 | 要提前做分库分表,不能靠加从库解决写瓶颈 |
| 大事务、频繁 DDL | 不适用 | Undo 膨胀、MDL 排队,DDL 窗口难控 |
| 复杂分析、向量检索 | 不适用 | 应拆到 OLAP/向量系统,不要在主库硬跑 |
| 需要成熟托管和运维工具 | 适用 | 生态完整,排障资料多 |
扬长是把事务和点查留在 MySQL;避短是把分析、搜索、向量拆出去,并在业务早期就设计分片键和归档策略。
讲清楚
MySQL 监控要分四层看:连接层看 Threads_running 和连接使用率,判断是否在排队;InnoDB 层看缓冲池命中率、Innodb_data_pending_fsyncs、History list 和行锁等待,判断读放大、刷盘和 Undo 回收;复制层看 Seconds_Behind_Source、Relay_Log_Space、两个复制线程和 GTID 差集;SQL/OS 层看 digest 的扫描行数、临时表落盘和磁盘 await。雪崩不是单点,而是「长事务 → Undo 不回收 → 磁盘压力」和「命中率下降 → P99 上升 → 连接堆积 → 重试」两条正反馈。处置先限流、杀阻塞源,再定位,最后才扩容;扩容对锁等待和复制延迟通常无效。选型上,MySQL 适合单主 OLTP 和成熟运维体系,写扩展、大事务、分析与向量检索是它的边界。