前言

数据库监控是 DBA 日常工作的基石。一个成熟的监控体系不仅能在故障发生时快速定位根因,更重要的是能在问题恶化前发出预警,实现从被动响应到主动防御的转变。

MySQL 通过 SHOW GLOBAL STATUSSHOW GLOBAL VARIABLESinformation_schemaperformance_schema 以及 sys 库暴露了数百个运行时指标。面对如此庞杂的信息,关键不在于"监控的越多越好",而在于理解每个指标的物理含义、相互关联以及在不同业务场景下的合理阈值

本文将围绕以下八个维度,对 MySQL 监控指标体系进行系统性梳理:

维度 核心关注点
服务器级指标 实例存活性、吞吐量、负载趋势
连接与线程 并发压力、连接池健康度、异常断连
临时对象与慢查询 SQL 质量、优化器行为
锁与并发 表锁/行锁竞争、死锁频率
网络流量 带宽消耗、数据传输规模
InnoDB Buffer Pool 内存命中率、I/O 压力
文件与表缓存 文件描述符、表缓存效率
复制拓扑 主从健康、延迟、数据一致性

约定:本文中所有 SHOW GLOBAL STATUS 返回的变量统一称为"状态变量"(Status Variable),SHOW GLOBAL VARIABLES 返回的称为"系统变量"(System Variable)。状态变量为累计值或瞬时值,系统变量为配置参数


1. 服务器级指标

服务器级指标反映 MySQL 实例的整体运行状态和负载趋势,是监控体系的第一道防线。

1.1 Uptime —— 实例存活时间

1
SHOW GLOBAL STATUS LIKE 'Uptime';
属性 说明
类型 状态变量(瞬时值)
单位
含义 自 MySQL 服务启动以来经过的秒数

监控价值

Uptime 本身不直接指示性能问题,但它的突然归零是最强的异常信号——意味着实例发生了重启。在生产环境中,非计划重启往往伴随着 OOM Killer、崩溃恢复(crash recovery)或误操作。

最佳实践

  • 对 Uptime 设置下降型告警:当 Uptime < 前一次采集值时触发 Critical 告警
  • 结合 Uptime_since_flush_status 判断是否有人手动执行了 FLUSH STATUS
  • 在 Prometheus 中使用 mysql_global_status_uptime 结合 changes() 函数检测重启
# Prometheus 告警规则示例
alert: MySQLRestarted
expr: changes(mysql_global_status_uptime[10m]) > 0
for: 1m
labels:
  severity: critical
annotations:
  summary: "MySQL 实例发生重启"

1.2 Queries 与 Questions —— 吞吐量双指标

1
2
SHOW GLOBAL STATUS LIKE 'Queries';
SHOW GLOBAL STATUS LIKE 'Questions';

这两个指标经常被混淆,但它们的统计口径有本质区别:

指标 统计范围
Queries 服务器执行的所有语句总数,包括存储过程内部语句、COM_STMT_EXECUTE(预编译语句执行)等
Questions 客户端发送到服务器的语句总数,不包含存储过程内部语句

关键差异

1
2
3
4
-- 假设有一个存储过程包含 5 条 SQL
CALL my_procedure();
-- Questions +1(只计 CALL 本身)
-- Queries  +6(CALL + 存储过程内部 5 条)

实际应用

  • Questions 更适合用于衡量"客户端视角的负载"
  • Queries 更适合用于衡量"服务器实际执行的工作量"
  • 如果 Queries - Questions 差值很大,说明业务大量使用了存储过程或预编译语句

1.3 QPS 与 TPS —— 吞吐量速率指标

QPS(Queries Per Second)和 TPS(Transactions Per Second)不是 MySQL 原生暴露的状态变量,而是需要通过两次采样的差值计算得出:

1
2
3
4
5
-- QPS 计算(基于 Questions,客户端视角)
QPS = (Questions_t2 - Questions_t1) / (t2 - t1)

-- TPS 计算
TPS = ((Com_commit_t2 - Com_commit_t1) + (Com_rollback_t2 - Com_rollback_t1)) / (t2 - t1)

为什么 QPS 基于 Questions 而非 Queries?

在多数监控场景中,我们关心的是"客户端对数据库施加了多大压力",而存储过程内部语句属于服务器端的自主行为,使用 Questions 能更准确地反映外部负载。但如果业务大量使用存储过程,则应同时监控基于 Queries 的 QPS 以获取完整视图。

阈值参考

QPS 和 TPS 没有绝对的"标准值",需要根据业务建立基线(Baseline):

场景 典型 QPS 范围 说明
小型 Web 应用 500 ~ 2,000 读多写少,多为简单查询
中型业务系统 2,000 ~ 10,000 混合读写负载
高并发 OLTP 10,000 ~ 50,000+ 需要连接池 + 读写分离

告警策略

  • 静态阈值:设定 QPS 上限(如历史峰值的 1.5 倍),超过触发告警
  • 动态基线:使用环比(同一时间段前一天/前一周的值)检测异常波动
  • 趋势告警:QPS 持续上升且斜率超过阈值时预警,提前发现容量瓶颈

1.4 Com_* —— 语句类型分布

1
SHOW GLOBAL STATUS LIKE 'Com_%';

Com_* 系列变量记录了每种 SQL 命令的执行次数。关键的几个:

变量 含义
Com_select SELECT 语句执行次数
Com_insert INSERT 语句执行次数
Com_update UPDATE 语句执行次数
Com_delete DELETE 语句执行次数
Com_commit COMMIT 执行次数
Com_rollback ROLLBACK 执行次数
Com_begin 显式 BEGIN/START TRANSACTION 次数

读写比分析

1
2
3
-- 读写比计算
读比例 = Com_select / (Com_select + Com_insert + Com_update + Com_delete)
写比例 = 1 - 读比例

读写比是决定架构策略的关键依据:

  • 读比例 > 80%:适合读写分离架构,将读流量导向从库
  • 写比例 > 40%:需要关注 InnoDB redo log、binlog 写入性能
  • Com_rollback / Com_commit > 5%:事务回滚率偏高,需排查业务逻辑

2. 连接与线程指标

连接管理是 MySQL 最容易出问题的环节之一。连接数暴增、连接泄漏、频繁断连都是生产环境中的高频故障。

2.1 Threads_connected —— 当前活跃连接数

1
SHOW GLOBAL STATUS LIKE 'Threads_connected';
属性 说明
类型 状态变量(瞬时值)
含义 当前处于连接状态的客户端线程数

注意区分Threads_connected 包含所有已建立连接的线程,无论它们是否正在执行查询。一个空闲连接(Sleep 状态)也计入其中。

相关系统变量

1
SHOW GLOBAL VARIABLES LIKE 'max_connections';  -- 最大允许连接数,默认 151

告警阈值

使用率 = Threads_connected / max_connections × 100%

> 70%  → Warning(开始关注)
> 85%  → Critical(需要立即处理)
> 95%  → Emergency(即将耗尽,新连接会被拒绝)

连接耗尽时的现象

Threads_connected 达到 max_connections 时,新连接将收到 ERROR 1040 (HY000): Too many connections。此时即便是 DBA 也无法登录排查问题(除非预留了 extra_port 或使用 super_read_only 的超级用户连接)。

生产建议

  • 务必设置 extra_max_connections(Percona Server)或预留 SUPER 权限的管理连接
  • 应用层必须使用连接池,避免短连接的创建/销毁开销
  • 监控 SHOW PROCESSLIST 中 Sleep 状态超过 wait_timeout 的僵尸连接

2.2 Threads_running —— 当前并发执行线程数

1
SHOW GLOBAL STATUS LIKE 'Threads_running';
属性 说明
类型 状态变量(瞬时值)
含义 当前正在执行查询(非 Sleep)的线程数

Threads_running 是比 Threads_connected 更敏感的负载指标。大量连接处于 Sleep 状态时 Threads_connected 很高但系统未必有压力;而 Threads_running 突增则意味着数据库正在承受真实的并发负载。

经验公式

正常范围:Threads_running < CPU 核心数 × 2 ~ 3
告警阈值:Threads_running > CPU 核心数 × 5(持续 30 秒以上)

Threads_running 飙升的常见原因

原因 排查方向
慢查询堆积 检查 SHOW PROCESSLIST,找到执行时间长的查询
锁等待 检查 SHOW ENGINE INNODB STATUS 的 SEMAPHORES 和 TRANSACTIONS 段
主从延迟导致读打到主库 检查从库状态和读写分离中间件配置
突发流量 对比 QPS 曲线,确认是否为正常流量高峰

2.3 Max_used_connections —— 历史最大并发连接数

1
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
属性 说明
类型 状态变量(高水位标记)
含义 自实例启动以来,同时连接的最大客户端数

容量规划核心指标

连接余量比 = Max_used_connections / max_connections × 100%

> 80%  → 需要考虑增大 max_connections 或优化连接池
< 10%  → max_connections 可能设置过大,浪费内存

内存影响:每个连接即使处于 Sleep 状态,也会占用一定的内存资源。MySQL 为每个连接分配的内存包括:

内存分配 大小 说明
线程栈 thread_stack(默认 256K ~ 1M) 每个连接固定分配
排序缓冲 sort_buffer_size(默认 256K) 需要排序时按需分配
连接缓冲 read_buffer_size(默认 128K) 全表扫描时分配
结果缓冲 net_buffer_length(默认 16K) 发送结果时分配

如果 max_connections 设置为 5000,而大量连接同时执行排序操作,仅 sort_buffer_size 一项就可能消耗 5000 × 256K ≈ 1.2G 内存。

2.4 Aborted_clients 与 Aborted_connects —— 异常断连

1
2
SHOW GLOBAL STATUS LIKE 'Aborted_clients';
SHOW GLOBAL STATUS LIKE 'Aborted_connects';
指标 含义
Aborted_clients 客户端成功连接后未正常关闭就断开的次数(如超时、网络闪断、程序崩溃)
Aborted_connects 尝试连接 MySQL 失败的次数(如密码错误、权限不足、max_connections 已满)

二者的本质区别

  • Aborted_connects:连接建立阶段失败,客户端从未成功登录
  • Aborted_clients:连接使用阶段异常断开,客户端已经成功登录但未正常退出

告警逻辑

Aborted_connects 增长率 > 10次/分钟 → 告警(可能在遭受暴力破解或应用配置错误)
Aborted_clients  增长率 > 50次/分钟 → 告警(应用连接池配置问题或网络不稳定)

排查 Aborted_connects 高的常见原因

  1. 应用配置了错误的密码或用户名
  2. 来自未授权 IP 的连接尝试(检查 host_cache 表或错误日志)
  3. max_connections 已满导致新连接被拒
  4. SSL/TLS 握手失败

排查 Aborted_clients 高的常见原因

  1. 客户端没有调用 mysql_close() 就退出
  2. wait_timeout / interactive_timeout 设置过短,空闲连接被服务器端强制断开
  3. 网络中间设备(如负载均衡器)的空闲超时比 MySQL 短
  4. PHP 短连接在脚本结束时未正常释放

2.5 Connections —— 累计连接尝试数

1
SHOW GLOBAL STATUS LIKE 'Connections';
属性 说明
类型 状态变量(累计值)
含义 自实例启动以来,尝试连接 MySQL 的总次数(无论成功或失败)

连接创建频率

1
连接创建速率 = (Connections_t2 - Connections_t1) / (t2 - t1)  -- 次/秒

如果连接创建速率持续很高(如 > 100次/秒),说明应用没有使用连接池或连接池配置不当,大量使用短连接。这会带来:

  • TCP 三次握手 + MySQL 认证的额外开销
  • 频繁创建和销毁线程的 CPU 消耗
  • Threads_created 指标持续增长

线程缓存优化

1
2
3
4
5
6
SHOW GLOBAL STATUS LIKE 'Threads_created';
SHOW GLOBAL VARIABLES LIKE 'thread_cache_size';

-- 线程缓存命中率
线程缓存命中率 = (1 - Threads_created / Connections) × 100%
-- 低于 95% 需要增大 thread_cache_size

3. 临时对象与慢查询指标

临时对象的使用情况直接反映 SQL 查询的效率。当优化器无法在内存中完成排序、去重、分组等操作时,会创建临时表甚至将临时表写入磁盘,这是性能下降的重要信号。

3.1 Created_tmp_tables —— 内存临时表创建次数

1
SHOW GLOBAL STATUS LIKE 'Created_tmp_tables';
属性 说明
类型 状态变量(累计值)
含义 查询执行过程中创建的内部临时表总数(包括内存和磁盘)

MySQL 在以下场景会创建内部临时表:

  • GROUP BY 的列没有索引
  • ORDER BYGROUP BY 使用了不同的列
  • DISTINCT 结合 ORDER BY
  • 使用了 UNION(非 UNION ALL
  • 派生表(子查询出现在 FROM 子句中)
  • 某些窗口函数操作

创建内存临时表本身不一定是问题,关键在于它是否被升级为磁盘临时表。

3.2 Created_tmp_disk_tables —— 磁盘临时表创建次数

1
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
属性 说明
类型 状态变量(累计值)
含义 因内存临时表超限而转换为磁盘临时表的次数

磁盘临时表的代价:磁盘 I/O 比内存访问慢 3~5 个数量级。一个本来可以在几毫秒内完成的查询,因为磁盘临时表可能膨胀到几百毫秒甚至几秒。

磁盘临时表比例

1
2
3
4
磁盘临时表比例 = Created_tmp_disk_tables / Created_tmp_tables × 100%

> 25%   需要关注和优化
> 50%   严重问题,需要立即排查

内存临时表转磁盘的触发条件

  1. 临时表大小超过 MIN(tmp_table_size, max_heap_table_size)
  2. 临时表包含 TEXTBLOB 列(MySQL 8.0.13 之前直接使用磁盘)
  3. 临时表包含大于 512 字节的 VARCHAR 列(在某些引擎中)

优化方向

1
2
3
4
5
6
7
-- 检查当前配置
SHOW GLOBAL VARIABLES LIKE 'tmp_table_size';      -- 默认 16M
SHOW GLOBAL VARIABLES LIKE 'max_heap_table_size';  -- 默认 16M

-- 适当增大(注意是 per-query 分配,不宜过大)
SET GLOBAL tmp_table_size = 64 * 1024 * 1024;       -- 64M
SET GLOBAL max_heap_table_size = 64 * 1024 * 1024;   -- 64M

关键认知:调大 tmp_table_size 只是"兜底",根本解决方案是优化 SQL——通过添加合适的索引避免临时表的创建。

3.3 Created_tmp_files —— 临时文件创建次数

1
SHOW GLOBAL STATUS LIKE 'Created_tmp_files';
属性 说明
类型 状态变量(累计值)
含义 MySQL 在操作系统层面创建的临时文件数量

Created_tmp_filesCreated_tmp_disk_tables 不同。前者是 MySQL 内部引擎(如排序算法、binlog 缓存溢出)在文件系统层面创建的临时文件,后者是 SQL 层临时表使用的磁盘引擎。

常见触发场景

  • 大排序操作超出 sort_buffer_size 时的外部排序
  • binlog 缓存溢出(binlog_cache_size 不足时写入临时文件)
  • LOAD DATA 操作的中间文件

监控建议Created_tmp_files 增长速率不宜过快,但绝对值参考意义有限。应结合 sort_buffer_sizebinlog_cache_disk_use 联合分析。

3.4 Slow_queries —— 慢查询累计次数

1
SHOW GLOBAL STATUS LIKE 'Slow_queries';
属性 说明
类型 状态变量(累计值)
含义 执行时间超过 long_query_time 的查询总数

相关系统变量

1
2
3
4
SHOW GLOBAL VARIABLES LIKE 'long_query_time';       -- 慢查询阈值,默认 10 秒
SHOW GLOBAL VARIABLES LIKE 'slow_query_log';         -- 是否开启慢查询日志
SHOW GLOBAL VARIABLES LIKE 'slow_query_log_file';    -- 慢查询日志文件路径
SHOW GLOBAL VARIABLES LIKE 'log_queries_not_using_indexes';  -- 是否记录未使用索引的查询

生产环境推荐配置

1
2
3
4
5
[mysqld]
slow_query_log = ON
long_query_time = 1            # 1 秒,而非默认的 10 秒
log_queries_not_using_indexes = ON
log_throttle_queries_not_using_indexes = 60  # 每分钟最多记录 60 条

为什么 long_query_time 要设为 1 秒而非默认的 10 秒?

对于 OLTP 系统,一条查询超过 1 秒已经是"慢"了。10 秒的默认值源自早期 MySQL 版本,当时硬件性能有限。在现代 SSD + 充足内存的环境下,保持 10 秒的阈值会让大量有优化空间的查询逃过监控。

慢查询速率告警

慢查询速率 = (Slow_queries_t2 - Slow_queries_t1) / (t2 - t1)

> 5 次/分钟  → Warning
> 20 次/分钟 → Critical

慢查询分析工具链

工具 用途
mysqldumpslow MySQL 自带,按模式聚合慢查询
pt-query-digest Percona Toolkit,功能最强大的慢查询分析工具
sys.statement_analysis MySQL 8.0 sys 库,基于 Performance Schema 的实时统计
1
2
3
4
5
# pt-query-digest 使用示例
pt-query-digest /var/log/mysql/slow.log \
  --limit=20 \
  --order-by=Query_time:sum \
  --output=report

4. 锁与并发指标

锁竞争是数据库性能瓶颈的头号嫌疑人。MySQL 的锁体系分为表级锁行级锁两层,在 InnoDB 引擎下还涉及意向锁、间隙锁、自增锁等细粒度机制。

4.1 Table_locks_immediate 与 Table_locks_waited

1
2
SHOW GLOBAL STATUS LIKE 'Table_locks_immediate';
SHOW GLOBAL STATUS LIKE 'Table_locks_waited';
指标 含义
Table_locks_immediate 请求表锁后立即获得的次数
Table_locks_waited 请求表锁后需要等待才能获得的次数

表锁等待比例

1
2
3
4
表锁等待比例 = Table_locks_waited / (Table_locks_immediate + Table_locks_waited) × 100%

> 0.5%   需要关注
> 2%     存在严重的表锁竞争

容易误解的地方

在纯 InnoDB 环境下,Table_locks_waited 应该非常低甚至为 0。如果这个值持续增长,常见原因包括:

  1. 仍在使用 MyISAM 引擎的表(如某些系统表或历史遗留表)
  2. 显式 LOCK TABLES 语句
  3. ALTER TABLE 等 DDL 操作(在 Online DDL 不支持的场景下会持有元数据锁)
  4. 隐式表锁:即便 InnoDB 使用行锁,MySQL Server 层在某些操作中仍会获取表级意向锁

4.2 InnoDB 行锁指标(引申补充)

对于 InnoDB 引擎,行级锁指标比表级锁更有诊断价值:

1
SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
指标 含义
Innodb_row_lock_current_waits 当前正在等待行锁的线程数
Innodb_row_lock_waits 行锁等待的累计次数
Innodb_row_lock_time 行锁等待的累计时间(毫秒)
Innodb_row_lock_time_avg 行锁等待的平均时间(毫秒)
Innodb_row_lock_time_max 行锁等待的最长时间(毫秒)

告警策略

Innodb_row_lock_current_waits > 10(持续 30 秒) → Critical
Innodb_row_lock_time_avg > 500ms                  → Warning
Innodb_row_lock_waits 增长速率 > 100次/分钟       → Warning

深入排查行锁问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- MySQL 8.0:通过 performance_schema 查看当前锁等待
SELECT
  r.trx_id AS waiting_trx_id,
  r.trx_mysql_thread_id AS waiting_thread,
  r.trx_query AS waiting_query,
  b.trx_id AS blocking_trx_id,
  b.trx_mysql_thread_id AS blocking_thread,
  b.trx_query AS blocking_query
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx r ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID
JOIN information_schema.innodb_trx b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID;

4.3 死锁监控(引申补充)

1
SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks';  -- MySQL 8.0.18+

在低版本中,死锁次数没有直接的状态变量,需要通过以下方式监控:

1
2
3
4
5
6
7
-- 方法一:解析 SHOW ENGINE INNODB STATUS
SHOW ENGINE INNODB STATUS\G
-- 在 LATEST DETECTED DEADLOCK 段查看最近的死锁信息

-- 方法二:开启死锁日志
SET GLOBAL innodb_print_all_deadlocks = ON;
-- 所有死锁信息将写入 error log

死锁告警策略

死锁增长速率 > 1次/分钟  → Warning(检查事务设计)
死锁增长速率 > 10次/分钟 → Critical(业务逻辑可能存在严重的锁竞争)

减少死锁的编码规范

  1. 固定加锁顺序:在一个事务中按主键升序访问行
  2. 缩短事务持有时间:将非数据库操作移到事务外部
  3. 使用合适的隔离级别READ COMMITTEDREPEATABLE READ 产生更少的间隙锁
  4. 控制事务大小:避免单个事务修改过多行

4.4 元数据锁(MDL)监控(引申补充)

元数据锁(Metadata Lock)是 MySQL 5.5 引入的机制,用于保护表结构在查询执行期间不被修改。MDL 问题在生产环境中非常常见,典型场景是:

一个长时间运行的 SELECT 持有 MDL 读锁 → DBA 执行 ALTER TABLE 需要 MDL 写锁 → 所有后续查询排队等待 MDL 读锁 → 雪崩

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- MySQL 8.0:查看 MDL 锁等待
SELECT
  object_type,
  object_schema,
  object_name,
  lock_type,
  lock_duration,
  lock_status,
  owner_thread_id
FROM performance_schema.metadata_locks
WHERE lock_status = 'PENDING';

MDL 监控建议

  • 开启 performance_schema.metadata_locks 表的消费者
  • 对 DDL 操作前检查是否存在长事务:SELECT * FROM information_schema.innodb_trx WHERE trx_started < NOW() - INTERVAL 60 SECOND
  • 使用 pt-online-schema-changegh-ost 替代原生 ALTER TABLE

5. 网络流量指标

网络流量指标帮助 DBA 了解数据库与客户端之间的数据传输规模,对带宽规划、慢查询排查(大结果集)和安全审计都有重要价值。

5.1 Bytes_received 与 Bytes_sent

1
2
SHOW GLOBAL STATUS LIKE 'Bytes_received';
SHOW GLOBAL STATUS LIKE 'Bytes_sent';
指标 含义
Bytes_received MySQL 从所有客户端接收到的字节总数(即客户端发送的 SQL 语句和数据)
Bytes_sent MySQL 向所有客户端发送的字节总数(即查询结果和响应)

流量速率计算

1
2
3
4
5
-- 接收速率(客户端 → MySQL)
入站流量 = (Bytes_received_t2 - Bytes_received_t1) / (t2 - t1)  -- bytes/s

-- 发送速率(MySQL → 客户端)
出站流量 = (Bytes_sent_t2 - Bytes_sent_t1) / (t2 - t1)  -- bytes/s

流量比分析

出站/入站比 = Bytes_sent / Bytes_received
出站/入站比 含义
> 10:1 典型的读密集型业务(小 SQL 请求 → 大结果集)
≈ 1:1 可能是大量 INSERT/LOAD DATA 操作(入站数据量大)
极高(> 100:1) 存在未加 LIMIT 的全表查询,返回了海量数据

告警建议

  • Bytes_sent 速率突增:可能有查询返回了异常大的结果集,检查是否有 SELECT *WHERE 条件的查询
  • Bytes_received 速率突增:可能正在进行大批量 INSERTLOAD DATA INFILE 操作
  • 流量达到网卡带宽的 70%+ 时应告警,特别是在主从复制架构中,binlog 传输也消耗带宽

5.2 Com_* 语句统计(与网络流量的关联)

在第 1 章已介绍了 Com_* 的基本含义,这里从网络流量的角度做补充分析。

每条语句平均消耗的网络带宽

1
2
平均请求大小 = Bytes_received / Questions    -- 每条查询的平均入站字节
平均响应大小 = Bytes_sent / Questions          -- 每条查询的平均出站字节

如果"平均响应大小"持续偏大(如 > 10KB/query),说明存在以下可能:

  1. SELECT 返回了不必要的列(应改为只查需要的字段)
  2. 缺少 LIMIT 限制,返回了过多行
  3. TEXT/BLOB 字段被不必要地包含在结果集中
  4. 未启用结果集压缩(--compress 选项)

在主从复制场景下的网络考量

主库的 binlog 传输到从库的流量也应纳入监控。特别是跨机房复制场景下,binlog 流量可能成为专线带宽瓶颈:

1
2
3
4
5
-- 主库上查看 binlog 写入量
SHOW GLOBAL STATUS LIKE 'Binlog_cache_disk_use';
SHOW GLOBAL STATUS LIKE 'Binlog_cache_use';
SHOW GLOBAL STATUS LIKE 'Binlog_stmt_cache_disk_use';
SHOW GLOBAL STATUS LIKE 'Binlog_stmt_cache_use';

6. InnoDB Buffer Pool 指标

InnoDB Buffer Pool 是 MySQL 最核心的内存组件,所有 InnoDB 表的数据页和索引页都缓存在其中。Buffer Pool 的命中率直接决定了数据库的 I/O 性能——命中就是内存读取(纳秒级),未命中就要从磁盘加载(毫秒级)。

6.1 innodb_buffer_pool_size —— Buffer Pool 总大小

1
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
属性 说明
类型 系统变量(配置参数)
单位 字节
含义 InnoDB 用于缓存数据页和索引页的内存区域大小

sizing 经验法则

场景 推荐大小
专用数据库服务器 物理内存的 70% ~ 80%
与应用共享服务器 物理内存的 50% ~ 60%
容器化部署 容器内存限制的 60% ~ 75%

为什么不能设为 100%?

操作系统本身需要内存,MySQL 的其他内存组件(连接缓冲、排序缓冲、binlog 缓存等)也需要内存。如果 Buffer Pool 占用过多,操作系统可能触发 OOM Killer 杀掉 mysqld 进程。

MySQL 8.0 支持在线调整

1
2
3
SET GLOBAL innodb_buffer_pool_size = 8 * 1024 * 1024 * 1024;  -- 调整为 8G
-- 可以通过以下方式监控调整进度:
SHOW STATUS LIKE 'Innodb_buffer_pool_resize_status';

6.2 Innodb_buffer_pool_read_requests —— 逻辑读次数

1
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
属性 说明
类型 状态变量(累计值)
含义 InnoDB 从 Buffer Pool 中读取数据页的请求总次数(即逻辑读

每一次查询需要访问数据页时,InnoDB 首先在 Buffer Pool 中查找。如果找到(缓存命中),Innodb_buffer_pool_read_requests 计数器 +1。

6.3 Innodb_buffer_pool_reads —— 物理读次数

1
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
属性 说明
类型 状态变量(累计值)
含义 InnoDB 无法从 Buffer Pool 满足的读请求次数,需要从磁盘加载数据页(即物理读

6.4 Buffer Pool 命中率 —— 最关键的衍生指标

1
命中率 = (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) × 100%

阈值标准

命中率 状态 行动
> 99.5% 优秀 无需调整
99% ~ 99.5% 良好 关注趋势
95% ~ 99% 警告 考虑增大 Buffer Pool 或优化查询
< 95% 危险 立即排查:Buffer Pool 太小、冷启动、或全表扫描大表

命中率低的常见原因分析

原因 表现 解决方案
Buffer Pool 过小 稳定但低于 99% 增大 innodb_buffer_pool_size
冷启动 重启后命中率低,逐渐恢复 使用 innodb_buffer_pool_dump_at_shutdown + innodb_buffer_pool_load_at_startup 预热
全表扫描大表 命中率周期性骤降 优化查询添加索引、限制结果集
工作集超出内存 持续低命中率 数据归档或分库分表减小工作集

Buffer Pool 预热配置

1
2
3
4
[mysqld]
innodb_buffer_pool_dump_at_shutdown = ON   # 关闭时保存 Buffer Pool 页面列表
innodb_buffer_pool_load_at_startup = ON    # 启动时加载上次保存的页面
innodb_buffer_pool_dump_pct = 75           # 保存 75% 最热的页面(默认 25%)

6.5 Buffer Pool 补充指标

1
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
指标 含义 监控价值
Innodb_buffer_pool_pages_total Buffer Pool 总页数 容量基准
Innodb_buffer_pool_pages_data 包含数据的页数 实际使用量
Innodb_buffer_pool_pages_dirty 脏页数量 写入压力指标
Innodb_buffer_pool_pages_free 空闲页数量 内存余量
Innodb_buffer_pool_wait_free 等待空闲页的次数 > 0 说明 Buffer Pool 严重不足
Innodb_buffer_pool_pages_flushed 刷新到磁盘的页数(累计) 写入 I/O 压力

脏页比例监控

1
脏页比例 = Innodb_buffer_pool_pages_dirty / Innodb_buffer_pool_pages_total × 100%

脏页比例过高(> 75%)可能导致:

  1. 检查点(Checkpoint)频繁触发,产生大量随机写 I/O
  2. 关闭数据库时需要长时间刷脏页(innodb_fast_shutdown = 0
  3. 崩溃恢复时间变长

相关配置参数:

1
2
SHOW GLOBAL VARIABLES LIKE 'innodb_max_dirty_pages_pct';      -- 默认 90%,触发激进刷脏的阈值
SHOW GLOBAL VARIABLES LIKE 'innodb_max_dirty_pages_pct_lwm';  -- 默认 10%,触发预刷脏的低水位

6.6 InnoDB I/O 补充指标

1
2
3
4
5
6
SHOW GLOBAL STATUS LIKE 'Innodb_data_reads';     -- 物理读操作次数
SHOW GLOBAL STATUS LIKE 'Innodb_data_writes';    -- 物理写操作次数
SHOW GLOBAL STATUS LIKE 'Innodb_data_read';      -- 读取的数据量(字节)
SHOW GLOBAL STATUS LIKE 'Innodb_data_written';   -- 写入的数据量(字节)
SHOW GLOBAL STATUS LIKE 'Innodb_data_fsyncs';    -- fsync 操作次数
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written'; -- redo log 写入量(字节)

IOPS 计算

1
2
 IOPS = (Innodb_data_reads_t2 - Innodb_data_reads_t1) / (t2 - t1)
 IOPS = (Innodb_data_writes_t2 - Innodb_data_writes_t1) / (t2 - t1)

结合磁盘硬件的 IOPS 上限(SSD 一般 10K100K IOPS,HDD 约 100200 IOPS),可以判断存储层是否成为瓶颈。


7. 文件与表缓存指标

MySQL 需要打开文件描述符来访问表数据文件、索引文件、日志文件等。当文件描述符资源不足或表缓存效率低下时,会产生额外的系统调用开销。

7.1 Open_files —— 当前打开的文件数

1
SHOW GLOBAL STATUS LIKE 'Open_files';
属性 说明
类型 状态变量(瞬时值)
含义 MySQL 当前打开的非临时文件数量

7.2 Opened_files —— 累计打开过的文件数

1
SHOW GLOBAL STATUS LIKE 'Opened_files';
属性 说明
类型 状态变量(累计值)
含义 自实例启动以来,打开文件的累计次数

如果 Opened_files 增长速率持续很高,说明文件缓存效率低,MySQL 在频繁地打开和关闭文件。

7.3 Open_files_limit —— 文件描述符上限

1
SHOW GLOBAL VARIABLES LIKE 'open_files_limit';
属性 说明
类型 系统变量(只读,启动时确定)
含义 操作系统允许 mysqld 进程使用的最大文件描述符数量

文件描述符使用率

1
2
3
4
文件描述符使用率 = Open_files / open_files_limit × 100%

> 75%   Warning(需要增大限制)
> 90%   Critical(可能导致 "Too many open files" 错误)

常见的文件描述符耗尽场景

  1. 数据库中有大量表(每个 InnoDB 表至少需要 1 个 .ibd 文件描述符)
  2. 分区表过多(每个分区对应独立的文件描述符)
  3. innodb_file_per_table = ON(默认)时表数量很大
  4. 操作系统层面的 ulimit -n 限制过低

调整方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. 查看当前操作系统限制
ulimit -n

# 2. 临时调整(当前会话)
ulimit -n 65535

# 3. 永久调整(/etc/security/limits.conf)
mysql soft nofile 65535
mysql hard nofile 65535

# 4. systemd 服务单元中设置
# 在 [Service] 段添加:
LimitNOFILE=65535

7.4 Table Cache(表缓存)指标

表缓存是 MySQL Server 层维护的一个缓存结构,用于缓存已打开表的文件描述符和元数据。避免每次查询都重新打开表文件。

1
2
3
4
5
6
SHOW GLOBAL VARIABLES LIKE 'table_open_cache';        -- 表缓存大小(默认 4000)
SHOW GLOBAL STATUS LIKE 'Open_tables';                 -- 当前打开的表数量
SHOW GLOBAL STATUS LIKE 'Opened_tables';               -- 累计打开过的表数量
SHOW GLOBAL STATUS LIKE 'Table_open_cache_hits';       -- 表缓存命中次数
SHOW GLOBAL STATUS LIKE 'Table_open_cache_misses';     -- 表缓存未命中次数
SHOW GLOBAL STATUS LIKE 'Table_open_cache_overflows';  -- 表缓存溢出次数

表缓存命中率

1
2
3
表缓存命中率 = Table_open_cache_hits / (Table_open_cache_hits + Table_open_cache_misses) × 100%

< 85%   需要增大 table_open_cache

表缓存使用率

1
2
3
表缓存使用率 = Open_tables / table_open_cache × 100%

> 95%   需要增大 table_open_cache

表缓存的 sizing 原则

table_open_cache ≈ 数据库中的表总数 × max_connections / 100

但这只是经验估算,实际应根据 Opened_tables 的增长速率和 Table_open_cache_overflows 来动态调整。

table_open_cache_instances 的作用

在高并发环境下,表缓存本身可能成为竞争点(因为需要加互斥锁)。table_open_cache_instances(默认 16)将表缓存分片为多个独立的互斥锁区域,减少锁竞争:

1
2
SHOW GLOBAL VARIABLES LIKE 'table_open_cache_instances';
-- 建议设为 16 或 32(高并发场景)

7.5 table_definition_cache —— 表定义缓存(引申补充)

1
2
3
SHOW GLOBAL VARIABLES LIKE 'table_definition_cache';   -- 默认值取决于表数量
SHOW GLOBAL STATUS LIKE 'Open_table_definitions';      -- 当前缓存的表定义数
SHOW GLOBAL STATUS LIKE 'Opened_table_definitions';    -- 累计打开的表定义数

table_definition_cache 缓存的是 .frm 文件(MySQL 5.7)或数据字典(MySQL 8.0)中的表定义信息。与 table_open_cache 不同,它缓存的是表结构元数据而非打开的文件句柄。

在表数量很多(> 10000)的场景下,应确保 table_definition_cache 足够大以容纳所有表定义。


8. 复制拓扑监控指标

在主从复制(Replication)架构中,复制健康状态的监控至关重要。复制中断或延迟过大会直接影响数据一致性和读写分离的可靠性。

8.1 复制状态核心指标

1
2
3
4
5
-- MySQL 5.7
SHOW SLAVE STATUS\G

-- MySQL 8.0.22+
SHOW REPLICA STATUS\G
指标 含义 正常值
Slave_IO_Running IO 线程是否在运行(从主库拉取 binlog) Yes
Slave_SQL_Running SQL 线程是否在运行(回放 relay log) Yes
Seconds_Behind_Master 从库落后主库的秒数 0 或接近 0
Last_IO_Error IO 线程最后的错误信息
Last_SQL_Error SQL 线程最后的错误信息
Last_IO_Errno IO 线程最后的错误号 0
Last_SQL_Errno SQL 线程最后的错误号 0

告警策略

Slave_IO_Running  != 'Yes'  → Critical(立即告警)
Slave_SQL_Running != 'Yes'  → Critical(立即告警)
Seconds_Behind_Master > 30  → Warning
Seconds_Behind_Master > 300 → Critical
Seconds_Behind_Master = NULL → Critical(复制可能已断开)

8.2 Seconds_Behind_Master 的局限性

Seconds_Behind_Master(SBM)是最常用的延迟指标,但它有几个重要的局限性需要理解:

1. SBM 的计算原理

SBM = 当前时间 - SQL 线程正在执行的事件的时间戳

这意味着:

  • 如果 SQL 线程暂停了(如手动 STOP SLAVE SQL_THREAD),SBM 会持续增长
  • 如果 IO 线程落后于主库,但 SQL 线程已经回放完所有已接收的 relay log,SBM 显示 0——但实际上是有延迟的
  • 如果主库上有一个大事务执行了 600 秒,SBM 在事务结束前一直是 0,事务提交后瞬间跳到 600

2. 多源复制场景

在多源复制(Multi-Source Replication)中,每个复制通道(channel)有独立的 SBM:

1
SHOW REPLICA STATUS FOR CHANNEL 'channel_name'\G

3. 更精确的延迟监控方案

方案 原理 优势
pt-heartbeat 在主库定期写入心跳时间戳,从库读取并计算差值 精确度高,不受 SBM 的各种偏差影响
GTID 位点差 比较主库 @@gtid_executed 与从库的差集 反映未同步的事务数量,而非时间
performance_schema.replication_applier_status MySQL 8.0 原生支持 无需额外工具,信息全面

pt-heartbeat 部署

1
2
3
4
5
6
# 主库上运行守护进程(每秒写入心跳)
pt-heartbeat --database=percona --update --create-table --daemonize

# 从库上检查延迟
pt-heartbeat --database=percona --monitor
# 输出示例:0.02s, 0.01s, 0.01s, 0.02s

8.3 GTID 相关监控(引申补充)

GTID(Global Transaction Identifier)模式下的复制监控比传统基于文件位点的复制更精确:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- 主库执行的 GTID 集合
SELECT @@global.gtid_executed;

-- 从库已执行的 GTID 集合
SELECT @@global.gtid_executed;

-- 从库已接收但未执行的 GTID(MySQL 8.0)
SELECT RECEIVED_TRANSACTION_SET 
FROM performance_schema.replication_connection_status;

-- 计算 GTID 差集(从库落后的事务数)
SELECT GTID_SUBTRACT(
  (SELECT @@global.gtid_executed FROM ... ),  -- 主库 GTID
  @@global.gtid_executed                       -- 从库 GTID
) AS lag_gtids;

GTID 监控的优势

  1. 可以精确知道从库落后了多少个事务,而非模糊的"秒数"
  2. 不受主库空闲期的影响(主库无写入时 SBM 始终为 0,无法判断是否真的同步)
  3. 多源复制中可以分别追踪每个源的 GTID 进度

8.4 复制过滤与通道监控(引申补充)

1
2
3
4
5
6
-- 检查复制过滤规则
SHOW REPLICA STATUS\G
-- 关注以下字段:
-- Replicate_Do_DB, Replicate_Ignore_DB
-- Replicate_Do_Table, Replicate_Ignore_Table
-- Replicate_Wild_Do_Table, Replicate_Wild_Ignore_Table

需要监控的复制异常场景

错误码 含义 常见原因
1062 Duplicate entry 从库上有人手动插入了数据
1032 Can’t find record 从库上有人手动删除了数据
1146 Table doesn’t exist DDL 执行顺序问题或复制过滤配置
1236 Could not find binlog 主库 binlog 已被清理
2003 Can’t connect to MySQL 网络问题或主库宕机

8.5 半同步复制监控(引申补充)

如果启用了半同步复制(Semi-synchronous Replication),还需要监控以下指标:

1
SHOW GLOBAL STATUS LIKE 'Rpl_semi_sync%';
指标 含义 告警阈值
Rpl_semi_sync_master_status 半同步是否开启 OFF 时告警
Rpl_semi_sync_master_no_tx 未等待从库确认就提交的事务数 持续增长需关注
Rpl_semi_sync_master_yes_tx 等待从库确认后提交的事务数 正常应持续增长
Rpl_semi_sync_master_tx_avg_wait_time 事务平均等待确认时间 > 1000μs 需检查网络

半同步降级告警

当从库响应超时(rpl_semi_sync_master_timeout),半同步会自动降级为异步复制,此时 Rpl_semi_sync_master_status 变为 OFF。这意味着数据安全性降低,必须立即告警:

Rpl_semi_sync_master_status = OFF → Critical
Rpl_semi_sync_master_no_tx 持续增长 → Warning(可能频繁降级)

9. 补充高级指标

除了前面八大维度的核心指标外,以下指标在特定场景下同样具有重要的诊断价值。

9.1 InnoDB Redo Log 与写入性能

1
2
3
4
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written';        -- redo log 累计写入字节
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';              -- 因 redo log buffer 不足等待的次数
SHOW GLOBAL STATUS LIKE 'Innodb_log_write_requests';     -- redo log 写入请求次数
SHOW GLOBAL STATUS LIKE 'Innodb_log_writes';             -- redo log 实际写入次数
指标 告警条件 含义
Innodb_log_waits > 0 且持续增长 Warning innodb_log_buffer_size 可能不足
redo log 写入速率接近磁盘带宽 Warning 写入性能瓶颈

redo log 大小计算

MySQL 8.0.30+ 支持动态调整 redo log 大小:

1
2
3
4
5
SHOW GLOBAL VARIABLES LIKE 'innodb_redo_log_capacity';  -- MySQL 8.0.30+
-- 旧版本
SHOW GLOBAL VARIABLES LIKE 'innodb_log_file_size';
SHOW GLOBAL VARIABLES LIKE 'innodb_log_files_in_group';
-- 总 redo log 容量 = innodb_log_file_size × innodb_log_files_in_group

9.2 InnoDB 自适应哈希索引(AHI)

1
2
SHOW GLOBAL STATUS LIKE 'Innodb_adaptive_hash%';
SHOW ENGINE INNODB STATUS\G  -- 查看 INSERT BUFFER AND ADAPTIVE HASH INDEX 段

AHI 是 InnoDB 自动为频繁访问的索引页构建的哈希索引。在某些工作负载下可能造成竞争:

1
2
-- 如果 SEMAPHORES 段显示大量 btr0sea.cc 相关的等待,考虑关闭 AHI
SET GLOBAL innodb_adaptive_hash_index = OFF;

9.3 Handler_* 操作统计

1
SHOW GLOBAL STATUS LIKE 'Handler_%';
指标 含义 监控价值
Handler_read_first 读取索引第一条记录的次数 全索引扫描的标志
Handler_read_key 基于索引键读取的次数 索引利用率的正面指标
Handler_read_next 顺序读取索引下一条的次数 范围扫描的标志
Handler_read_rnd 基于固定位置读取行的次数 排序后回表读取
Handler_read_rnd_next 全表扫描时逐行读取的次数 全表扫描的明确标志

索引利用率评估

1
索引利用率 = Handler_read_key / (Handler_read_key + Handler_read_rnd_next) × 100%

如果 Handler_read_rnd_next 值非常大且持续增长,说明存在大量全表扫描查询,需要结合慢查询日志找出具体 SQL 并添加索引。

9.4 排序相关指标

1
SHOW GLOBAL STATUS LIKE 'Sort_%';
指标 含义
Sort_rows 累计排序的行数
Sort_scan 通过全表扫描进行排序的次数
Sort_range 通过范围扫描进行排序的次数
Sort_merge_passes 排序合并的趟数(外部排序的标志)

Sort_merge_passes > 0 且持续增长说明排序数据量超出了 sort_buffer_size,MySQL 使用了外部排序(写磁盘临时文件后多路归并)。可以适当增大 sort_buffer_size,但更根本的方案是优化查询避免大数据量排序。

9.5 Select_* 查询方式统计

1
SHOW GLOBAL STATUS LIKE 'Select_%';
指标 含义 优化方向
Select_scan 全表扫描次数 添加索引
Select_full_join 无索引的 JOIN 全表扫描次数 最危险的指标之一,必须优化
Select_range 使用范围索引的查询次数 正常行为
Select_range_check JOIN 后检查键使用的次数 索引选择性不佳
Select_full_range_join 使用范围搜索的 JOIN 次数 检查 JOIN 条件

Select_full_join > 0 必须告警——这意味着有 JOIN 查询没有使用任何索引,在大表上这是灾难性的。


10. 监控体系搭建建议

有了指标的理论基础,还需要一套可落地的监控体系。以下是生产环境中主流的 MySQL 监控方案:

10.1 监控技术栈选型

方案 组成 适用场景
Prometheus + Grafana mysqld_exporter + Prometheus + Grafana 云原生/容器化环境,最主流的开源方案
Zabbix Zabbix Agent + MySQL 监控模板 传统运维团队,资产管理集成需求
PMM(Percona Monitoring and Management) 基于 Prometheus + Grafana + QAN 深度 MySQL/MongoDB 监控,开箱即用
商业方案 Datadog / New Relic / SolarWinds 预算充足,需要 SaaS 化管理

10.2 Prometheus + mysqld_exporter 核心配置

1
2
3
4
5
6
7
8
# prometheus.yml 片段
scrape_configs:
  - job_name: 'mysql'
    scrape_interval: 15s
    static_configs:
      - targets: ['mysql-host:9104']
        labels:
          instance: 'prod-master'

mysqld_exporter 关键启动参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mysqld_exporter \
  --config.my-cnf=/etc/.mysqld_exporter.cnf \
  --collect.global_status \
  --collect.global_variables \
  --collect.slave_status \
  --collect.info_schema.innodb_metrics \
  --collect.info_schema.processlist \
  --collect.info_schema.tables \
  --collect.perf_schema.tableiowaits \
  --collect.perf_schema.tablelocks

10.3 告警分级体系

一套合理的告警体系应该分为三个级别:

级别 触发条件 响应时效 通知方式
P0 - Emergency 实例宕机、复制中断、磁盘满 5 分钟内响应 电话 + 短信 + IM
P1 - Critical 连接池耗尽、Buffer Pool 命中率 < 95%、主从延迟 > 5 分钟 30 分钟内响应 短信 + IM
P2 - Warning 慢查询增多、磁盘临时表比例高、锁等待增加 工作时间内处理 IM + 邮件

10.4 监控指标速查总表

维度 指标 类型 告警阈值
存活性 Uptime 瞬时值 突然归零 → P0
吞吐量 QPS(基于 Questions) 速率 > 历史峰值 1.5 倍 → P2
连接数 Threads_connected / max_connections 比率 > 85% → P1
并发度 Threads_running 瞬时值 > CPU×5 持续30s → P1
连接异常 Aborted_connects 增长率 速率 > 10/min → P2
磁盘临时表 Created_tmp_disk_tables / Created_tmp_tables 比率 > 25% → P2
慢查询 Slow_queries 增长率 速率 > 20/min → P1
表锁等待 Table_locks_waited 占比 比率 > 2% → P2
行锁等待 Innodb_row_lock_current_waits 瞬时值 > 10 持续30s → P1
死锁 死锁增长率 速率 > 1/min → P2
网络出站 Bytes_sent 速率 速率 > 网卡70% → P2
BP 命中率 1 - reads/read_requests 比率 < 95% → P1
BP 空闲页 Innodb_buffer_pool_wait_free 累计 > 0 → P1
文件描述符 Open_files / open_files_limit 比率 > 90% → P1
表缓存 Table_open_cache_misses 速率 速率 持续高 → P2
IO 线程 Slave_IO_Running 状态 != Yes → P0
SQL 线程 Slave_SQL_Running 状态 != Yes → P0
主从延迟 Seconds_Behind_Master 瞬时值 > 300s → P1
半同步 Rpl_semi_sync_master_status 状态 OFF → P1

11. 总结

MySQL 监控指标体系不是一个静态的清单,而是一个需要根据业务特点持续演进的动态系统。本文梳理的核心要点:

11.1 指标分层思维

第一层:存活性指标(Uptime、复制状态)
   └── 回答"数据库还活着吗?"

第二层:容量指标(连接数、Buffer Pool、文件描述符)
   └── 回答"资源还够用吗?"

第三层:性能指标(QPS、锁等待、慢查询、命中率)
   └── 回答"数据库健康吗?"

第四层:趋势指标(QPS 趋势、连接数趋势、数据量增长)
   └── 回答"未来会不会出问题?"

11.2 监控的三个原则

  1. 不要只监控绝对值,要监控变化率Slow_queries = 10000 不一定有问题(可能跑了一年),但 Slow_queries 增长了 500/分钟 就是严重异常。

  2. 不要只监控单一指标,要看关联指标Threads_running 飙升时,应同时检查 Innodb_row_lock_current_waits(是否锁等待)、Slow_queries(是否慢查询堆积)、QPS(是否流量突增)。

  3. 不要只依赖监控,还要有基线:没有基线的监控就是盲人摸象。花时间建立每个指标在正常业务周期(工作日、周末、月结、促销)下的基线值,告警才有意义。

11.3 从指标到行动的映射

现象 排查路径
应用报 “Too many connections” Threads_connected → max_connections → processlist(Sleep 连接过多?)→ 连接池配置
应用响应变慢 Threads_running → Slow_queries → Innodb_row_lock_waits → Buffer Pool 命中率 → 磁盘 I/O
主从延迟突增 SBM → 主库 binlog 写入量 → 从库 SQL 线程状态 → 大事务检查 → 并行复制配置
数据库 CPU 100% Threads_running → processlist → 慢查询日志 → EXPLAIN 分析 → 索引优化
OOM 被杀 Buffer Pool 大小 → max_connections × per-session 内存 → 临时表内存 → 操作系统可用内存

监控的终极目标不是收集数据,而是在正确的时间向正确的人传递正确的信息,驱动正确的行动