Files
cs-note/hhs/MySQL/08-工程实践/38-监控指标.md
T

263 lines
12 KiB
Markdown
Raw Normal View History

2026-05-24 11:42:38 +08:00
---
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 级别的慢查询追踪