Files
cs-note/hhs/MySQL/08-工程实践/38-监控指标.md
T
2026-05-24 11:42:38 +08:00

263 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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/03-索引与查询优化/16-慢查询日志分析]] — pt-query-digest 配合监控系统
- [[hhs/Redis/09-高级特性]] — Redis 监控指标的对比
- [[hhs/GORM/16-日志与调试]] — GORM 级别的慢查询追踪