前言
数据库监控是 DBA 日常工作的基石。一个成熟的监控体系不仅能在故障发生时快速定位根因,更重要的是能在问题恶化前发出预警,实现从被动响应到主动防御的转变。
MySQL 通过 SHOW GLOBAL STATUS、SHOW GLOBAL VARIABLES、information_schema、performance_schema 以及 sys 库暴露了数百个运行时指标。面对如此庞杂的信息,关键不在于"监控的越多越好",而在于理解每个指标的物理含义、相互关联以及在不同业务场景下的合理阈值。
本文将围绕以下八个维度,对 MySQL 监控指标体系进行系统性梳理:
| 维度 | 核心关注点 |
|---|---|
| 服务器级指标 | 实例存活性、吞吐量、负载趋势 |
| 连接与线程 | 并发压力、连接池健康度、异常断连 |
| 临时对象与慢查询 | SQL 质量、优化器行为 |
| 锁与并发 | 表锁/行锁竞争、死锁频率 |
| 网络流量 | 带宽消耗、数据传输规模 |
| InnoDB Buffer Pool | 内存命中率、I/O 压力 |
| 文件与表缓存 | 文件描述符、表缓存效率 |
| 复制拓扑 | 主从健康、延迟、数据一致性 |
约定:本文中所有
SHOW GLOBAL STATUS返回的变量统一称为"状态变量"(Status Variable),SHOW GLOBAL VARIABLES返回的称为"系统变量"(System Variable)。状态变量为累计值或瞬时值,系统变量为配置参数。
1. 服务器级指标
服务器级指标反映 MySQL 实例的整体运行状态和负载趋势,是监控体系的第一道防线。
1.1 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 —— 吞吐量双指标
| |
这两个指标经常被混淆,但它们的统计口径有本质区别:
| 指标 | 统计范围 |
|---|---|
| Queries | 服务器执行的所有语句总数,包括存储过程内部语句、COM_STMT_EXECUTE(预编译语句执行)等 |
| Questions | 客户端发送到服务器的语句总数,不包含存储过程内部语句 |
关键差异:
| |
实际应用:
- Questions 更适合用于衡量"客户端视角的负载"
- Queries 更适合用于衡量"服务器实际执行的工作量"
- 如果
Queries - Questions差值很大,说明业务大量使用了存储过程或预编译语句
1.3 QPS 与 TPS —— 吞吐量速率指标
QPS(Queries Per Second)和 TPS(Transactions Per Second)不是 MySQL 原生暴露的状态变量,而是需要通过两次采样的差值计算得出:
| |
为什么 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_* —— 语句类型分布
| |
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 次数 |
读写比分析:
| |
读写比是决定架构策略的关键依据:
- 读比例 > 80%:适合读写分离架构,将读流量导向从库
- 写比例 > 40%:需要关注 InnoDB redo log、binlog 写入性能
Com_rollback / Com_commit> 5%:事务回滚率偏高,需排查业务逻辑
2. 连接与线程指标
连接管理是 MySQL 最容易出问题的环节之一。连接数暴增、连接泄漏、频繁断连都是生产环境中的高频故障。
2.1 Threads_connected —— 当前活跃连接数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(瞬时值) |
| 含义 | 当前处于连接状态的客户端线程数 |
注意区分:Threads_connected 包含所有已建立连接的线程,无论它们是否正在执行查询。一个空闲连接(Sleep 状态)也计入其中。
相关系统变量:
| |
告警阈值:
使用率 = 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 —— 当前并发执行线程数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(瞬时值) |
| 含义 | 当前正在执行查询(非 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 —— 历史最大并发连接数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(高水位标记) |
| 含义 | 自实例启动以来,同时连接的最大客户端数 |
容量规划核心指标:
连接余量比 = 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 —— 异常断连
| |
| 指标 | 含义 |
|---|---|
| Aborted_clients | 客户端成功连接后未正常关闭就断开的次数(如超时、网络闪断、程序崩溃) |
| Aborted_connects | 尝试连接 MySQL 失败的次数(如密码错误、权限不足、max_connections 已满) |
二者的本质区别:
Aborted_connects:连接建立阶段失败,客户端从未成功登录Aborted_clients:连接使用阶段异常断开,客户端已经成功登录但未正常退出
告警逻辑:
Aborted_connects 增长率 > 10次/分钟 → 告警(可能在遭受暴力破解或应用配置错误)
Aborted_clients 增长率 > 50次/分钟 → 告警(应用连接池配置问题或网络不稳定)
排查 Aborted_connects 高的常见原因:
- 应用配置了错误的密码或用户名
- 来自未授权 IP 的连接尝试(检查
host_cache表或错误日志) max_connections已满导致新连接被拒- SSL/TLS 握手失败
排查 Aborted_clients 高的常见原因:
- 客户端没有调用
mysql_close()就退出 wait_timeout/interactive_timeout设置过短,空闲连接被服务器端强制断开- 网络中间设备(如负载均衡器)的空闲超时比 MySQL 短
- PHP 短连接在脚本结束时未正常释放
2.5 Connections —— 累计连接尝试数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | 自实例启动以来,尝试连接 MySQL 的总次数(无论成功或失败) |
连接创建频率:
| |
如果连接创建速率持续很高(如 > 100次/秒),说明应用没有使用连接池或连接池配置不当,大量使用短连接。这会带来:
- TCP 三次握手 + MySQL 认证的额外开销
- 频繁创建和销毁线程的 CPU 消耗
Threads_created指标持续增长
线程缓存优化:
| |
3. 临时对象与慢查询指标
临时对象的使用情况直接反映 SQL 查询的效率。当优化器无法在内存中完成排序、去重、分组等操作时,会创建临时表甚至将临时表写入磁盘,这是性能下降的重要信号。
3.1 Created_tmp_tables —— 内存临时表创建次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | 查询执行过程中创建的内部临时表总数(包括内存和磁盘) |
MySQL 在以下场景会创建内部临时表:
GROUP BY的列没有索引ORDER BY与GROUP BY使用了不同的列DISTINCT结合ORDER BY- 使用了
UNION(非UNION ALL) - 派生表(子查询出现在
FROM子句中) - 某些窗口函数操作
创建内存临时表本身不一定是问题,关键在于它是否被升级为磁盘临时表。
3.2 Created_tmp_disk_tables —— 磁盘临时表创建次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | 因内存临时表超限而转换为磁盘临时表的次数 |
磁盘临时表的代价:磁盘 I/O 比内存访问慢 3~5 个数量级。一个本来可以在几毫秒内完成的查询,因为磁盘临时表可能膨胀到几百毫秒甚至几秒。
磁盘临时表比例:
| |
内存临时表转磁盘的触发条件:
- 临时表大小超过
MIN(tmp_table_size, max_heap_table_size) - 临时表包含
TEXT或BLOB列(MySQL 8.0.13 之前直接使用磁盘) - 临时表包含大于 512 字节的
VARCHAR列(在某些引擎中)
优化方向:
| |
关键认知:调大
tmp_table_size只是"兜底",根本解决方案是优化 SQL——通过添加合适的索引避免临时表的创建。
3.3 Created_tmp_files —— 临时文件创建次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | MySQL 在操作系统层面创建的临时文件数量 |
Created_tmp_files 与 Created_tmp_disk_tables 不同。前者是 MySQL 内部引擎(如排序算法、binlog 缓存溢出)在文件系统层面创建的临时文件,后者是 SQL 层临时表使用的磁盘引擎。
常见触发场景:
- 大排序操作超出
sort_buffer_size时的外部排序 - binlog 缓存溢出(
binlog_cache_size不足时写入临时文件) LOAD DATA操作的中间文件
监控建议:Created_tmp_files 增长速率不宜过快,但绝对值参考意义有限。应结合 sort_buffer_size 和 binlog_cache_disk_use 联合分析。
3.4 Slow_queries —— 慢查询累计次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | 执行时间超过 long_query_time 的查询总数 |
相关系统变量:
| |
生产环境推荐配置:
| |
为什么
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 的实时统计 |
| |
4. 锁与并发指标
锁竞争是数据库性能瓶颈的头号嫌疑人。MySQL 的锁体系分为表级锁和行级锁两层,在 InnoDB 引擎下还涉及意向锁、间隙锁、自增锁等细粒度机制。
4.1 Table_locks_immediate 与 Table_locks_waited
| |
| 指标 | 含义 |
|---|---|
| Table_locks_immediate | 请求表锁后立即获得的次数 |
| Table_locks_waited | 请求表锁后需要等待才能获得的次数 |
表锁等待比例:
| |
容易误解的地方:
在纯 InnoDB 环境下,Table_locks_waited 应该非常低甚至为 0。如果这个值持续增长,常见原因包括:
- 仍在使用 MyISAM 引擎的表(如某些系统表或历史遗留表)
- 显式
LOCK TABLES语句 ALTER TABLE等 DDL 操作(在 Online DDL 不支持的场景下会持有元数据锁)- 隐式表锁:即便 InnoDB 使用行锁,MySQL Server 层在某些操作中仍会获取表级意向锁
4.2 InnoDB 行锁指标(引申补充)
对于 InnoDB 引擎,行级锁指标比表级锁更有诊断价值:
| |
| 指标 | 含义 |
|---|---|
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
深入排查行锁问题:
| |
4.3 死锁监控(引申补充)
| |
在低版本中,死锁次数没有直接的状态变量,需要通过以下方式监控:
| |
死锁告警策略:
死锁增长速率 > 1次/分钟 → Warning(检查事务设计)
死锁增长速率 > 10次/分钟 → Critical(业务逻辑可能存在严重的锁竞争)
减少死锁的编码规范:
- 固定加锁顺序:在一个事务中按主键升序访问行
- 缩短事务持有时间:将非数据库操作移到事务外部
- 使用合适的隔离级别:
READ COMMITTED比REPEATABLE READ产生更少的间隙锁 - 控制事务大小:避免单个事务修改过多行
4.4 元数据锁(MDL)监控(引申补充)
元数据锁(Metadata Lock)是 MySQL 5.5 引入的机制,用于保护表结构在查询执行期间不被修改。MDL 问题在生产环境中非常常见,典型场景是:
一个长时间运行的 SELECT 持有 MDL 读锁 → DBA 执行 ALTER TABLE 需要 MDL 写锁 → 所有后续查询排队等待 MDL 读锁 → 雪崩
| |
MDL 监控建议:
- 开启
performance_schema.metadata_locks表的消费者 - 对 DDL 操作前检查是否存在长事务:
SELECT * FROM information_schema.innodb_trx WHERE trx_started < NOW() - INTERVAL 60 SECOND - 使用
pt-online-schema-change或gh-ost替代原生ALTER TABLE
5. 网络流量指标
网络流量指标帮助 DBA 了解数据库与客户端之间的数据传输规模,对带宽规划、慢查询排查(大结果集)和安全审计都有重要价值。
5.1 Bytes_received 与 Bytes_sent
| |
| 指标 | 含义 |
|---|---|
| Bytes_received | MySQL 从所有客户端接收到的字节总数(即客户端发送的 SQL 语句和数据) |
| Bytes_sent | MySQL 向所有客户端发送的字节总数(即查询结果和响应) |
流量速率计算:
| |
流量比分析:
出站/入站比 = Bytes_sent / Bytes_received
| 出站/入站比 | 含义 |
|---|---|
| > 10:1 | 典型的读密集型业务(小 SQL 请求 → 大结果集) |
| ≈ 1:1 | 可能是大量 INSERT/LOAD DATA 操作(入站数据量大) |
| 极高(> 100:1) | 存在未加 LIMIT 的全表查询,返回了海量数据 |
告警建议:
Bytes_sent速率突增:可能有查询返回了异常大的结果集,检查是否有SELECT *无WHERE条件的查询Bytes_received速率突增:可能正在进行大批量INSERT或LOAD DATA INFILE操作- 流量达到网卡带宽的 70%+ 时应告警,特别是在主从复制架构中,binlog 传输也消耗带宽
5.2 Com_* 语句统计(与网络流量的关联)
在第 1 章已介绍了 Com_* 的基本含义,这里从网络流量的角度做补充分析。
每条语句平均消耗的网络带宽:
| |
如果"平均响应大小"持续偏大(如 > 10KB/query),说明存在以下可能:
SELECT返回了不必要的列(应改为只查需要的字段)- 缺少
LIMIT限制,返回了过多行 TEXT/BLOB字段被不必要地包含在结果集中- 未启用结果集压缩(
--compress选项)
在主从复制场景下的网络考量:
主库的 binlog 传输到从库的流量也应纳入监控。特别是跨机房复制场景下,binlog 流量可能成为专线带宽瓶颈:
| |
6. InnoDB Buffer Pool 指标
InnoDB Buffer Pool 是 MySQL 最核心的内存组件,所有 InnoDB 表的数据页和索引页都缓存在其中。Buffer Pool 的命中率直接决定了数据库的 I/O 性能——命中就是内存读取(纳秒级),未命中就要从磁盘加载(毫秒级)。
6.1 innodb_buffer_pool_size —— Buffer Pool 总大小
| |
| 属性 | 说明 |
|---|---|
| 类型 | 系统变量(配置参数) |
| 单位 | 字节 |
| 含义 | InnoDB 用于缓存数据页和索引页的内存区域大小 |
sizing 经验法则:
| 场景 | 推荐大小 |
|---|---|
| 专用数据库服务器 | 物理内存的 70% ~ 80% |
| 与应用共享服务器 | 物理内存的 50% ~ 60% |
| 容器化部署 | 容器内存限制的 60% ~ 75% |
为什么不能设为 100%?
操作系统本身需要内存,MySQL 的其他内存组件(连接缓冲、排序缓冲、binlog 缓存等)也需要内存。如果 Buffer Pool 占用过多,操作系统可能触发 OOM Killer 杀掉 mysqld 进程。
MySQL 8.0 支持在线调整:
| |
6.2 Innodb_buffer_pool_read_requests —— 逻辑读次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | InnoDB 从 Buffer Pool 中读取数据页的请求总次数(即逻辑读) |
每一次查询需要访问数据页时,InnoDB 首先在 Buffer Pool 中查找。如果找到(缓存命中),Innodb_buffer_pool_read_requests 计数器 +1。
6.3 Innodb_buffer_pool_reads —— 物理读次数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | InnoDB 无法从 Buffer Pool 满足的读请求次数,需要从磁盘加载数据页(即物理读) |
6.4 Buffer Pool 命中率 —— 最关键的衍生指标
| |
阈值标准:
| 命中率 | 状态 | 行动 |
|---|---|---|
| > 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 预热配置:
| |
6.5 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 压力 |
脏页比例监控:
| |
脏页比例过高(> 75%)可能导致:
- 检查点(Checkpoint)频繁触发,产生大量随机写 I/O
- 关闭数据库时需要长时间刷脏页(
innodb_fast_shutdown = 0) - 崩溃恢复时间变长
相关配置参数:
| |
6.6 InnoDB I/O 补充指标
| |
IOPS 计算:
| |
结合磁盘硬件的 IOPS 上限(SSD 一般 10K100K IOPS,HDD 约 100200 IOPS),可以判断存储层是否成为瓶颈。
7. 文件与表缓存指标
MySQL 需要打开文件描述符来访问表数据文件、索引文件、日志文件等。当文件描述符资源不足或表缓存效率低下时,会产生额外的系统调用开销。
7.1 Open_files —— 当前打开的文件数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(瞬时值) |
| 含义 | MySQL 当前打开的非临时文件数量 |
7.2 Opened_files —— 累计打开过的文件数
| |
| 属性 | 说明 |
|---|---|
| 类型 | 状态变量(累计值) |
| 含义 | 自实例启动以来,打开文件的累计次数 |
如果 Opened_files 增长速率持续很高,说明文件缓存效率低,MySQL 在频繁地打开和关闭文件。
7.3 Open_files_limit —— 文件描述符上限
| |
| 属性 | 说明 |
|---|---|
| 类型 | 系统变量(只读,启动时确定) |
| 含义 | 操作系统允许 mysqld 进程使用的最大文件描述符数量 |
文件描述符使用率:
| |
常见的文件描述符耗尽场景:
- 数据库中有大量表(每个 InnoDB 表至少需要 1 个
.ibd文件描述符) - 分区表过多(每个分区对应独立的文件描述符)
innodb_file_per_table = ON(默认)时表数量很大- 操作系统层面的
ulimit -n限制过低
调整方法:
| |
7.4 Table Cache(表缓存)指标
表缓存是 MySQL Server 层维护的一个缓存结构,用于缓存已打开表的文件描述符和元数据。避免每次查询都重新打开表文件。
| |
表缓存命中率:
| |
表缓存使用率:
| |
表缓存的 sizing 原则:
table_open_cache ≈ 数据库中的表总数 × max_connections / 100
但这只是经验估算,实际应根据 Opened_tables 的增长速率和 Table_open_cache_overflows 来动态调整。
table_open_cache_instances 的作用:
在高并发环境下,表缓存本身可能成为竞争点(因为需要加互斥锁)。table_open_cache_instances(默认 16)将表缓存分片为多个独立的互斥锁区域,减少锁竞争:
| |
7.5 table_definition_cache —— 表定义缓存(引申补充)
| |
table_definition_cache 缓存的是 .frm 文件(MySQL 5.7)或数据字典(MySQL 8.0)中的表定义信息。与 table_open_cache 不同,它缓存的是表结构元数据而非打开的文件句柄。
在表数量很多(> 10000)的场景下,应确保 table_definition_cache 足够大以容纳所有表定义。
8. 复制拓扑监控指标
在主从复制(Replication)架构中,复制健康状态的监控至关重要。复制中断或延迟过大会直接影响数据一致性和读写分离的可靠性。
8.1 复制状态核心指标
| |
| 指标 | 含义 | 正常值 |
|---|---|---|
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:
| |
3. 更精确的延迟监控方案:
| 方案 | 原理 | 优势 |
|---|---|---|
pt-heartbeat | 在主库定期写入心跳时间戳,从库读取并计算差值 | 精确度高,不受 SBM 的各种偏差影响 |
| GTID 位点差 | 比较主库 @@gtid_executed 与从库的差集 | 反映未同步的事务数量,而非时间 |
performance_schema.replication_applier_status | MySQL 8.0 原生支持 | 无需额外工具,信息全面 |
pt-heartbeat 部署:
| |
8.3 GTID 相关监控(引申补充)
GTID(Global Transaction Identifier)模式下的复制监控比传统基于文件位点的复制更精确:
| |
GTID 监控的优势:
- 可以精确知道从库落后了多少个事务,而非模糊的"秒数"
- 不受主库空闲期的影响(主库无写入时 SBM 始终为 0,无法判断是否真的同步)
- 多源复制中可以分别追踪每个源的 GTID 进度
8.4 复制过滤与通道监控(引申补充)
| |
需要监控的复制异常场景:
| 错误码 | 含义 | 常见原因 |
|---|---|---|
| 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),还需要监控以下指标:
| |
| 指标 | 含义 | 告警阈值 |
|---|---|---|
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 与写入性能
| |
| 指标 | 告警条件 | 含义 |
|---|---|---|
Innodb_log_waits > 0 且持续增长 | Warning | innodb_log_buffer_size 可能不足 |
| redo log 写入速率接近磁盘带宽 | Warning | 写入性能瓶颈 |
redo log 大小计算:
MySQL 8.0.30+ 支持动态调整 redo log 大小:
| |
9.2 InnoDB 自适应哈希索引(AHI)
| |
AHI 是 InnoDB 自动为频繁访问的索引页构建的哈希索引。在某些工作负载下可能造成竞争:
| |
9.3 Handler_* 操作统计
| |
| 指标 | 含义 | 监控价值 |
|---|---|---|
Handler_read_first | 读取索引第一条记录的次数 | 全索引扫描的标志 |
Handler_read_key | 基于索引键读取的次数 | 索引利用率的正面指标 |
Handler_read_next | 顺序读取索引下一条的次数 | 范围扫描的标志 |
Handler_read_rnd | 基于固定位置读取行的次数 | 排序后回表读取 |
Handler_read_rnd_next | 全表扫描时逐行读取的次数 | 全表扫描的明确标志 |
索引利用率评估:
| |
如果 Handler_read_rnd_next 值非常大且持续增长,说明存在大量全表扫描查询,需要结合慢查询日志找出具体 SQL 并添加索引。
9.4 排序相关指标
| |
| 指标 | 含义 |
|---|---|
Sort_rows | 累计排序的行数 |
Sort_scan | 通过全表扫描进行排序的次数 |
Sort_range | 通过范围扫描进行排序的次数 |
Sort_merge_passes | 排序合并的趟数(外部排序的标志) |
Sort_merge_passes > 0 且持续增长说明排序数据量超出了 sort_buffer_size,MySQL 使用了外部排序(写磁盘临时文件后多路归并)。可以适当增大 sort_buffer_size,但更根本的方案是优化查询避免大数据量排序。
9.5 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 核心配置
| |
mysqld_exporter 关键启动参数:
| |
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 监控的三个原则
不要只监控绝对值,要监控变化率:
Slow_queries = 10000不一定有问题(可能跑了一年),但Slow_queries 增长了 500/分钟就是严重异常。不要只监控单一指标,要看关联指标:
Threads_running飙升时,应同时检查Innodb_row_lock_current_waits(是否锁等待)、Slow_queries(是否慢查询堆积)、QPS(是否流量突增)。不要只依赖监控,还要有基线:没有基线的监控就是盲人摸象。花时间建立每个指标在正常业务周期(工作日、周末、月结、促销)下的基线值,告警才有意义。
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 内存 → 临时表内存 → 操作系统可用内存 |
监控的终极目标不是收集数据,而是在正确的时间向正确的人传递正确的信息,驱动正确的行动。