--- tags: [MySQL, 监控指标, QPS, TPS, Buffer Pool Hit Rate, Prometheus, Grafana, InnoDB] create time: 2026-05-16 00:00 --- # 监控指标 ## 概述 有效的监控体系能让你在问题爆发前发现问题。MySQL 的监控涵盖 **性能**、**可用性**、**容量** 和 **安全** 四个维度。 > [!TIP] 核心思想 > 监控的目的不是「收集更多数据」,而是 **「用最少的关键指标发现最多的问题」**。初学者常犯的错误是把所有 SHOW STATUS 变量都记录下来——但真正需要告警的可能不到 20 个。先定义你的 SLA/SLO,再决定监控什么。 一个典型的 MySQL 监控栈自下而上: ```mermaid graph TD A["MySQL Server"] -->|"SHOW GLOBAL STATUS / performance_schema"| B["mysqld_exporter"] B -->|"HTTP scrape"| C["Prometheus"] C -->|"查询 + 告警规则"| D["Grafana"] C -->|"Alertmanager"| E["告警通道\n钉钉/飞书/PagerDuty"] D -->|"Dashboard"| F["运维 & 开发"] style A fill:#f9d,stroke:#333 style C fill:#bee,stroke:#333 style D fill:#ddf,stroke:#333 style E fill:#fdb,stroke:#333 ``` 理解了这个架构,你就能定位任何一个环节出问题时该检查哪里。 ## 核心指标速查 ### 吞吐量类 | 指标 | SHOW STATUS 名称 | 健康范围 | 说明 | |------|-----------------|---------|------| | **QPS** | `Queries` | 视硬件而定 | 包含内部重写(如 INSERT → multiple row),非客户端请求数 | | **TPS** | `Com_commit` + `Com_rollback` | 视硬件而定 | 只统计 InnoDB 事务型操作 | | **Threads Connected** | `Threads_connected` | < MaxConnections × 0.8 | 当前已建立的连接总数 | | **Threads Running** | `Threads_running` | < CPU 核心数 | 正在执行查询的线程数(瞬时高峰可短暂超限) | > [!WARNING] QPS ≠ 客户端请求数 > `Queries` 计数的是服务器内部处理的查询数,而非客户端发送的请求数。一条 `INSERT INTO t VALUES(1),(2),(3)` 会被计数为 1 次 Client Query,但可能被优化器展开后计为多次 Internal Queries。如果需要精确的客户端请求量,应看 `Com_select` + `Com_insert` + `Com_update` + `Com_delete` 的总和。 ```sql -- 计算实时 QPS 和 TPS SHOW GLOBAL STATUS LIKE 'Queries'; -- 累计查询数 SHOW GLOBAL STATUS LIKE 'Com_commit'; -- 累计 commit 数 SHOW GLOBAL STATUS LIKE 'Com_rollback'; -- 累计 rollback 数 -- QPS = Queries / Uptime -- TPS = (Com_commit + Com_rollback) / Uptime ``` ```go // Go 中采集指标的推荐方式:直接查询并返回 map func collectStatus(db *sql.DB) (map[string]string, error) { rows, err := db.Query("SHOW GLOBAL STATUS") if err != nil { return nil, err } defer rows.Close() metrics := make(map[string]string) for rows.Next() { var name, value string rows.Scan(&name, &value) // value 可能是数字或字符串,统一读取为 string metrics[name] = value } return metrics, rows.Err() } ``` 这段代码的核心思路是 **一次查询拿全部指标**,避免 N+1 次网络往返。实际生产环境中,你会在每个指标之间做差值运算得到速率,详见下方公式推导。 **速率计算公式:** ``` ΔQPS = (Queries_now - Queries_prev) / (Timestamp_now - Timestamp_prev) ΔTPS = ((Com_commit_now + Com_rollback_now) - (Com_commit_prev + Com_rollback_prev)) / ΔSeconds ``` Prometheus 的 `mysqld_exporter` 会自动帮你完成这个差值计算,如果用原生 SQL 采样则需自行实现。 ### InnoDB 引擎指标 InnoDB 是绝大多数 MySQL 业务场景使用的存储引擎,其内部指标决定了数据库的整体健康状况。 | 指标 | SHOW STATUS 名称 | 健康阈值 | 说明 | |------|-----------------|---------|------| | **Buffer Pool 读请求** | `Innodb_buffer_pool_read_requests` | — | 从 Buffer Pool 中读取的逻辑请求总数 | | **Buffer Pool 磁盘读取** | `Innodb_buffer_pool_reads` | 命中率 ≥ 99% | 需回磁盘读取的次数(未命中) | | **Redo Log 写入量** | `Innodb_data_written` | 突增意味着写入密集 | 累计写入 Redo Log 的数据字节数 | | **Redo Log flush 等待** | `Innodb_log_waits` | 应接近 0 | Redo Log 缓冲区满导致刷盘等待 | | **死锁次数** | `Innodb_deadlocks` | 不应持续增加 | 每次死锁回滚一个事务 | | **平均行锁等待** | `Innodb_row_lock_time_avg` | < 10ms | 行级锁平均等待时间 | | **行锁总等待时间** | `Innodb_row_lock_time` | 监控趋势 | 所有行锁等待累积耗时(ms) | | **DML 计数** | `Innodb_rows_deleted/inserted/updated` | 监控比例变化 | 各类 DML 操作的累计次数 | > [!NOTE] 如何判断 Buffer Pool 大小是否合理? > 只看命中率是不够的。更可靠的方法是观察 `Innodb_buffer_pool_reads` 的增长斜率:如果它几乎是一条水平线(单位时间内增长极少),说明当前 Buffer Pool 够用;如果斜率持续走高,就是增大 `innodb_buffer_pool_size` 的信号。一般建议设为物理内存的 **50%~70%**。 ```sql -- Buffer Pool 命中率(推荐写法) SELECT (1 - A.value / B.value) * 100 AS hit_rate_pct FROM (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') A, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') B; -- 为什么不用 INFORMATION_SCHEMA.INNODB_METRICS? -- 该表需要额外启用,且部分版本中字段名不一致,推荐使用 global_status 视图 ``` > [!QUESTION] 思考 > 假设你的 Buffer Pool 命中率为 99.5%,但这个值是从服务启动至今累计计算的。如果最近一小时内出现了缓存穿透,累计比率会显示出来吗? > **答案**:不会——累计值会稀释短期异常。正确的做法是用定时采集的 **差值** 来计算窗口内的实时命中率。 ### 复制延迟指标 主从复制是 MySQL 高可用的基石。延迟超过容忍度,读写分离就会带来数据一致性问题。 | 指标 | SHOW REPLICA / SLAVE STATUS 字段 | 告警阈值 | 说明 | |------|--------------------------------|---------|------| | **Seconds_Behind_Master** | `Seconds_Behind_Master` | > 5s 警告,> 30s 严重 | 从库落后主库的秒数(NULL 表示连接断开) | | **IO Thread** | `Slave_IO_Running` (5.7) / `Replica_IO_Running` (8.0) | 必须 = Yes | IO 线程负责拉取 binlog | | **SQL Thread** | `Slave_SQL_Running` (5.7) / `Replica_SQL_Running` (8.0) | 必须 = Yes | SQL 线程负责重放事件 | | **Relay Log 空间** | `Relay_Log_Space` | 突增预警 | 中继日志积压通常意味着大事务 | ```sql -- 标准查看方式(MySQL 8.0.22+ 使用 REPLICA 关键字) SHOW REPLICA STATUS\G -- MySQL 8.0+: 基于 GTID 的更精确延迟检测 SELECT PROCESSLIST_TIME AS delay_seconds, INFO FROM performance_schema.threads t JOIN performance_schema.events_stages_current ess ON t.THREAD_ID = ess.THREAD_ID WHERE NAME = 'stage/replication_sql_waiting_for_master_to_send_event'; ``` > [!CAUTION] Seconds_Behind_Master 的陷阱 > 当从库停止时,这个值会变成 NULL 而非一个巨大的数字。因此告警逻辑应同时检查 `Slave_IO_Running = No`、`Slave_SQL_Running = No` 和 `Seconds_Behind_Master IS NULL` 三种情况。 ## 慢查询趋势 慢查询日志不只是排障工具,更是 **长期性能趋势** 的风向标。每天慢查询数量的突然增加,往往预示着即将发生的系统性问题。 ```sql -- 每日慢查询统计 SELECT DATE_FORMAT(event_time, '%Y-%m-%d') AS day, COUNT(*) AS slow_count, ROUND(AVG(query_time), 3) AS avg_time, ROUND(MAX(query_time), 3) AS max_time FROM mysql.slow_log GROUP BY day ORDER BY day DESC LIMIT 30; ``` > [!TIP] 实操建议 > 在生产环境不建议直接查 `mysql.slow_log`(它是表格式存储,数据量大时性能差)。推荐使用 Percona 的 **[pt-query-digest](https://www.percona.com/doc/percona-toolkit/LATEST/pt-query-digest.html)**,它能聚合相似查询并按类型统计,还能输出 HTML 报告。 一个实用的告警规则设计: ```mermaid flowchart LR A["Slow Query 阈值\nquery_time > 1s"] --> B{"单条超时 > 10s?"} B -->|是| C["立即告警\nP1 级别"] B -->|否| D{"当日累计 > 昨日 2x?"} D -->|是| E["趋势告警\nP2 级别"] D -->|否| F["正常记录"] style C fill:#f99,stroke:#c00 style E fill:#fd9,stroke:#ca0 style F fill:#9f9,stroke:#0a0 ``` 这种分级告警能帮你区分「偶发异常」和「系统性恶化」,避免告警疲劳。 ## 告警分级策略 好的监控应该告诉你 **现在该做什么**,而不是让你从一堆告警里自己判断。 | 级别 | 触发条件示例 | 响应时效 | 通知渠道 | |------|-------------|---------|---------| | **P0 致命** | Slave_SQL_Running = No、磁盘空间 < 5%、Innodb_deadlocks > 0/min | 5 分钟内 | 电话 + 短信 + IM | | **P1 严重** | 延迟 > 30s、Threads_running > CPU×2、QPS 骤降 50%+ | 15 分钟内 | 短信 + IM | | **P2 警告** | 延迟 > 5s、命中率 < 95%、慢查询激增 | 1 小时内 | IM 群 | | **P3 提示** | Buffer Pool 利用率持续下降、表空间增长过快 | 当天处理 | 日报/周报 | > [!QUESTION] 你的 SLA 是什么? > 不同的业务对可用性的要求差异巨大:金融系统可能要求 99.999%,而内部工具 99.9% 就足够了。**先明确业务目标,再反过来设计告警阈值**。不要盲目套用别人的配置。 ## 监控工具选型 | 工具 | 定位 | 优点 | 缺点 | |------|------|------|------| | **Prometheus + mysqld_exporter** | 指标采集 | 云原生标准、告警丰富 | 需自建 Grafana Dashboard | | **PMM (Percona)** | 完整监控平台 | 开箱即用、包含 QAN 分析 | 较重,占用资源 | | **Zabbix** | 传统监控 | 成熟稳定、报警灵活 | 缺少查询级洞察 | | **Datadog/NewRelic** | SaaS 监控 | 零运维、可视化优秀 | 付费、数据出网 | | **阿里云 RDS 监控** | 云服务内置 | 免费、深度集成 | 锁定云厂商 | ### Prometheus + mysqld_exporter 快速搭建 ```yaml # prometheus.yml 追加 scrape_configs: - job_name: 'mysql' static_configs: - targets: ['localhost:9104'] # mysqld_exporter 端口 ``` ```bash # 启动 exporter(MySQL 5.7) mysqld_exporter \ --config.my-cnf=/etc/mysql/debian.cnf \ --collect.global_status \ --collect.engine_innodb_status \ --collect.perf_schema.tableiowaits # MySQL 8.0 推荐额外开启 perf_schema 指标 mysqld_exporter \ --config.my-cnf=/etc/mysql/debian.cnf \ --collect.global_status \ --collect.info_schema.innodb_metrics \ --collect.perf_schema.eventswaitssummary ``` > [!NOTE] mysqld_exporter 权限 > exporter 需要专门的只读账号,至少授予 `REPLICATION CLIENT`, `PROCESS`, `SUPER` 权限即可。切勿用 root 账号运行。 ## Grafana 关键 Dashboard 以下面板组合作为基线监控,可根据自身需求增减: ``` 1. Overview: QPS, TPS, Connections, Slow Queries 2. InnoDB Buffer Pool: Hit Rate, Dirty Pages, Free Pages 3. Replication: Seconds Behind Master, IO/SQL Thread Lag 4. Disk I/O: Read/Write Latency, Throughput 5. Network: Bytes Sent/Received per Second 6. Memory: Used vs Available ``` 社区推荐的 Dashboard ID(导入到 Grafana 即可): | 主题 | Dashboard ID | 作者 | |------|-------------|------| | MySQL Overview | [3131](https://grafana.com/grafana/dashboards/3131-mysql-database/) | Google | | Percona MySQL | [9967](https://grafana.com/grafana/dashboards/9967-percona-server-mysql-dashboard/) | Percona | | mysqld_exporter | [12234](https://grafana.com/grafana/dashboards/12234-mysqld-exporter-overview/) | Prometheus Community | ## 关联笔记 - [[hhs/MySQL/20-慢查询日志分析]] — pt-query-digest 配合监控系统 - [[hhs/Redis/09-高级特性]] — Redis 监控指标的对比 - [[hhs/GORM/16-日志与调试]] — GORM 级别的慢查询追踪