数据库是系统核心组件,其性能直接影响用户体验。建立完善的监控与诊断体系,是 DBA 和 SRE 的核心能力。本文系统讲解 MySQL 性能监控的完整工具链与实践方法。
1. 监控指标体系
1.1 黄金指标(Google SRE 方法论)
| 指标类型 | 具体指标 | 说明 |
|---|---|---|
| 流量 | QPS/TPS、连接数、Threads_connected | 系统负载 |
| 延迟 | 查询耗时 P50/P95/P99、慢查询率 | 响应速度 |
| 错误 | 错误查询数、死锁数、锁等待超时 | 失败比例 |
| 饱和度 | CPU、内存、磁盘 IO、Buffer Pool 命中率 | 资源瓶颈 |
1.2 关键公式
QPS = Queries / Uptime
TPS = (Com_commit + Com_rollback) / Uptime
Buffer Pool 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
值 > 95% 为健康
慢查询占比 = Slow_queries / Queries × 100%
应 < 1%,否则需要优化
2. Performance Schema
MySQL 5.5+ 内置的性能监控框架,低开销采集运行时数据。
2.1 启用配置
[mysqld]
performance_schema = ON
performance-schema-instrument='wait/lock/%=ON'
performance-schema-instrument='stage/%=ON'
performance-schema-instrument='statement/%=ON'
2.2 核心查询
-- 查看最消耗资源的 SQL(按总执行时间排序)
SELECT
DIGEST_TEXT as query,
COUNT_STAR as exec_count,
SUM_TIMER_WAIT/1e12 as total_latency_sec,
AVG_TIMER_WAIT/1e12 as avg_latency_sec,
MAX_TIMER_WAIT/1e12 as max_latency_sec,
SUM_LOCK_TIME/1e12 as total_lock_time_sec,
SUM_ROWS_SENT,
SUM_ROWS_EXAMINED,
FIRST_SEEN, LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
-- 查看表级别的 IO 统计
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
COUNT_READ, SUM_NUMBER_OF_BYTES_READ,
COUNT_WRITE, SUM_NUMBER_OF_BYTES_WRITE,
COUNT_FETCH, SUM_TIMER_FETCH/1e12
FROM performance_schema.table_io_waits_summary_by_table
ORDER BY SUM_TIMER_WAIT DESC;
-- 查看从未使用过的索引
SELECT
OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
AND COUNT_STAR = 0;
2.3 Sys Schema(Performance Schema 视图封装)
-- 查看全表扫描的 SQL
SELECT * FROM sys.statements_with_full_table_scans
ORDER BY rows_examined DESC;
-- 查看最慢的 SQL(75 percentile)
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile;
-- 查看锁等待
SELECT * FROM sys.innodb_lock_waits\G
-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查看表统计
SELECT * FROM sys.schema_table_statistics
WHERE table_schema = 'mydb' ORDER BY total_latency DESC;
3. 慢查询日志分析
3.1 配置慢查询日志
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 1 # 超过1秒记录
log_queries_not_using_indexes = 1 # 记录未走索引的查询
log_slow_admin_statements = 1
log_slow_slave_statements = 1
log_throttle_queries_not_using_indexes = 10 # 每分钟最多记录10条无索引查询
3.2 pt-query-digest 深度分析
# 基础分析
pt-query-digest /var/lib/mysql/slow.log > slow_report.txt
# 过滤特定数据库
pt-query-digest --filter '$event->{db} && $event->{db} eq "mydb"' slow.log
# 按时间段分析
pt-query-digest --since '2024-01-01 00:00:00' --until '2024-01-02 00:00:00' slow.log
# 输出到库里(方便查询历史)
pt-query-digest --review h=localhost,D=percona,t=query_review \
--history h=localhost,D=percona,t=query_history slow.log
3.3 pt-query-digest 报告关键信息
Profile
Rank Query ID Response time Calls R/Call V/M Item
==== ================== ============= ===== ====== ===== ====
1 0x1234... 1500s 45.0% 5000 0.3000 0.01 SELECT users
2 0x5678... 800s 24.0% 2000 0.4000 0.02 UPDATE orders
解读:
- Response time: 该指纹 SQL 的总耗时及占比
- Calls: 执行次数
- R/Call: 单次平均耗时
- V/M: 方差均值比,越高表示执行时间越不稳定
4. 锁与死锁诊断
4.1 查看当前锁
-- InnoDB 当前事务与锁
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
-- 查看锁明细
SELECT * FROM performance_schema.data_locks
WHERE OBJECT_NAME = 'my_table';
4.2 死锁分析
-- 查看最近死锁日志
SHOW ENGINE INNODB STATUS\G
-- ------------------------
-- LATEST DETECTED DEADLOCK
-- ------------------------
-- *** (1) TRANSACTION:
-- TRANSACTION 12345, ACTIVE 10 sec starting index read
-- mysql tables in use 1, locked 1
-- LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
-- MySQL thread id 100, OS thread handle 12345, query id 500 localhost 127.0.0.1 app
-- SELECT * FROM accounts WHERE id = 1 FOR UPDATE
-- *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
-- RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `db`.`accounts`
-- *** (2) TRANSACTION:
-- ...
死锁解决:
- 调整事务顺序(按固定顺序访问资源)
- 缩短事务时长
- 降低隔离级别
- 重试逻辑:
ON DUPLICATE KEY UPDATE或应用层重试
5. InnoDB 内部状态诊断
# InnoDB 完整状态
SHOW ENGINE INNODB STATUS\G
# 关键段分析
关键监控项:
| 段落 | 内容 | 健康指标 |
|---|---|---|
| SEMAPHORES | 信号量争用 | rw-shared spins < 1000/s |
| TRANSACTIONS | 活跃事务、锁信息 | 长事务 < 60s |
| FILE I/O | IO 线程状态 | pending_reads/pending_writes 低 |
| INSERT BUFFER AND ADAPTIVE HASH INDEX | Change Buffer/AHI | hash searches/s 高比例 |
| LOG | Redo Log 状态 | Log sequence 增长平稳 |
| BUFFER POOL AND MEMORY | Buffer Pool 使用 | hit rate > 95% |
| INDIVIDUAL BUFFER POOL INFO | 多实例 Buffer Pool | Free buffers 充足 |
6. 连接池监控与 SQL 执行计划分析
连接池是应用与数据库之间的缓冲层,连接池配置不当往往比数据库本身更容易成为瓶颈。
6.1 HikariCP 关键指标
| 指标 | 说明 | 健康阈值 |
|---|---|---|
activeConnections | 当前活跃连接数 | < maximumPoolSize 的 80% |
idleConnections | 空闲连接数 | 保持 minimumIdle 附近 |
pendingThreads | 等待获取连接的线程数 | = 0,持续 >0 表示池太小 |
totalConnections | 总连接数 | = maximumPoolSize 时饱和 |
maxLifetime / idleTimeout | 连接生命周期 | 建议 < 数据库 wait_timeout |
# Micrometer 暴露连接池指标
management:
metrics:
enable:
hikari: true # 自动暴露 HikariCP 指标到 Prometheus
连接池常见问题:
- 连接泄漏:应用获取连接后未关闭 →
pendingThreads持续增长。解决:检查try-with-resources或@Transactional边界。 - 池过小:高并发时连接等待 → 增大
maximumPoolSize(但不超过数据库max_connections的 20%)。 - 池过大:数据库
Threads_connected飙升 → 调小连接池或优化 SQL 减少长事务占用。
6.2 EXPLAIN 执行计划分析
EXPLAIN ANALYZE
SELECT o.order_id, u.name, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid'
AND o.create_time > '2024-01-01'
ORDER BY o.amount DESC
LIMIT 100;
EXPLAIN 关键字段解读:
| 字段 | 含义 | 注意点 |
|---|---|---|
type | 访问类型 | ALL = 全表扫描(危险),range/ref/eq_ref = 良好 |
key | 实际使用的索引 | 为 NULL 说明未走索引 |
rows | 估算扫描行数 | 与实际 rows_sent 差距大说明统计信息不准 |
Extra | 附加信息 | Using filesort/Using temporary 需优化 |
cost | 查询成本(MySQL 8.0) | 用于对比不同执行计划 |
执行计划优化路径:
- 检查
type = ALL→ 为 WHERE/ORDER BY 列添加索引 - 检查
Extra = Using filesort→ 建立包含 ORDER BY 字段的联合索引 - 检查
Extra = Using temporary→ 优化 GROUP BY / 子查询,避免临时表 - 检查
key = NULL→ 分析 WHERE 条件选择性,决定是否加索引
6.3 索引优化策略
| 场景 | 策略 | 示例 |
|---|---|---|
| 高频等值查询 | 单列索引 | INDEX(status) |
| 范围 + 排序 | 联合索引(范围字段在后) | INDEX(status, create_time) |
| 覆盖索引 | SELECT 字段全部在索引中 | INDEX(user_id, status, amount) 满足查询无需回表 |
| 前缀索引 | 长文本字段 | INDEX(email(10)) |
| 函数索引(MySQL 8.0+) | WHERE 中常对字段使用函数 | INDEX((LOWER(email))) |
索引维护检查:
-- 查看索引选择性(cardinality / rows,越接近 1 越好)
SELECT TABLE_NAME, INDEX_NAME, CARDINALITY,
CARDINALITY / (SELECT TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_NAME = t.TABLE_NAME) as selectivity
FROM information_schema.STATISTICS t
WHERE TABLE_SCHEMA = 'mydb';
-- 查看冗余索引(Sys Schema)
SELECT * FROM sys.schema_redundant_indexes;
7. 读写分离与分库分表监控
7.1 读写分离监控要点
| 检查项 | 方法 | 异常信号 |
|---|---|---|
| 主从延迟 | Seconds_Behind_Master | > 10 秒,读库数据陈旧 |
| 复制线程状态 | SHOW SLAVE STATUS | Slave_IO_Running/Slave_SQL_Running ≠ Yes |
| 读流量分布 | 代理层(ShardingSphere/MyCat)指标 | 读节点负载不均 |
| 主库写压力 | Com_insert + Com_update + Com_delete | QPS 接近单节点上限 |
主从延迟常见原因:
- 从库磁盘 IO 慢(检查
iostat -x 1) - 大事务在主库执行完才复制(将大事务拆分为小批量)
- 从库也有查询压力(考虑增加从库或启用
slave_parallel_workers)
7.2 分库分表监控差异
-- 分片键路由命中率(中间件层统计)
-- 理想情况 > 90% 的查询能直接路由到单分片
SELECT
total_queries,
routed_to_single_shard,
broadcast_queries,
routed_to_single_shard / total_queries as hit_rate
FROM sharding_proxy.metrics;
跨分片查询(broadcast_queries)会导致所有分片并发执行,耗时取最慢分片。监控中应关注跨分片查询占比,> 10% 说明分片键设计或查询模式有问题。
8. 云数据库 RDS 监控差异
| 维度 | 自建 MySQL | 云 RDS(AWS Aurora / 阿里云 RDS) |
|---|---|---|
| 基础指标 | 自行采集 | 内置 Dashboard,分钟级粒度 |
| 慢查询 | 需配置慢日志 + pt-query-digest | 控制台直接查看,支持可视化分析 |
| 锁诊断 | SHOW ENGINE INNODB STATUS | 提供死锁分析图表与历史记录 |
| 备份监控 | 自行实现(xtrabackup + cron) | 自动备份,需关注备份窗口与 RPO |
| 连接数 | 直接查数据库 | CloudWatch / 云监控 API |
| 主从延迟 | Seconds_Behind_Master | 提供秒级精确延迟指标 |
| 存储空间 | df / information_schema | 自动扩容预警 |
云数据库的隐藏成本项:
- IOPS 费用:SSD 云盘的 IOPS 超限后会限速
- 备份存储:自动备份占用额外存储空间
- 跨区域复制:Binlog 跨区域传输费用
- 性能洞察:Aurora Performance Insights / 阿里云 SQL 洞察通常按量付费
9. 可视化监控方案
6.1 PMM(Percona Monitoring and Management)
# Docker 部署
docker pull percona/pmm-server:2
docker run -d \
-p 443:443 \
-v /srv/pmm-data:/srv \
--name pmm-server \
--restart always \
percona/pmm-server:2
# 客户端注册
pmm-admin config --server-insecure-tls --server-url=https://admin:admin@pmm-server:443
pmm-admin add mysql --username=pmm --password=pmm --query-source=perfschema
PMM 关键 Dashboard:
- MySQL Overview: QPS、线程、连接、Buffer Pool
- InnoDB Metrics: 事务、锁、redo log、Adaptive Hash
- Query Analytics: Top Queries、Query Details
- Compare: 多实例对比
6.2 Prometheus + Grafana(自建)
# mysqld_exporter 配置
scrape_configs:
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
params:
collect[]:
- global_status
- perf_schema.tablelocks
- perf_schema.file_events
- binlog
- info_schema.innodb_metrics
Grafana Dashboard 推荐:
- MySQL Overview (ID: 7362)
- MySQL InnoDB Metrics (ID: 11323)
7. 自动化告警规则
# Prometheus alert rules
groups:
- name: mysql-alerts
rules:
- alert: MySQLHighQPS
expr: rate(mysql_global_status_queries[1m]) > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL QPS 过高"
- alert: MySQLSlowQueries
expr: rate(mysql_global_status_slow_queries[5m]) > 1
for: 5m
labels:
severity: warning
- alert: MySQLConnectionsHigh
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
for: 5m
labels:
severity: critical
- alert: MySQLReplicationLag
expr: mysql_slave_lag_seconds > 60
for: 5m
labels:
severity: critical
- alert: MySQLDeadlock
expr: increase(mysql_global_status_innodb_deadlocks[5m]) > 0
labels:
severity: warning
10. 急诊十分钟排查手册
当数据库突然异常(CPU 100%、连接打满、查询超时)时,按以下顺序排查:
第 1 分钟:确认范围
├── SHOW GLOBAL STATUS LIKE 'Threads_connected'
├── SHOW GLOBAL STATUS LIKE 'Queries'
├── 是单库问题还是全链路问题?(排除应用/网络/中间件)
└── 决策:自救 or 切从库 or 扩容
第 2-3 分钟:定位元凶
├── SHOW PROCESSLIST / performance_schema.threads
│ └── 找到执行时间最长的 SQL(Time 列)
├── 或用 sys.session 视图:SELECT * FROM sys.session ORDER BY current_statement_latency DESC
└── 记录 SQL 指纹(Digest)和线程 ID
第 4-5 分钟:止损
├── KILL <thread_id> 杀慢查询(谨慎!确认不是关键业务)
├── 若是缓存穿透:临时 LIMIT 和强制索引
├── 若是死锁:应用层重试 + 事务顺序优化
└── 若 CPU 高但无慢查询:检查是否处于统计信息更新期
第 6-10 分钟:根因分析与恢复
├── pt-query-digest 分析最近 5 分钟慢日志
├── 检查是否有新上线的 SQL(SHOW GLOBAL STATUS LIKE 'Com_%' 对比基线)
├── EXPLAIN 分析异常 SQL 的执行计划
├── 临时添加索引(Online DDL,MySQL 8.0+ 不锁表)
└── 更新监控告警阈值,防止再次遗漏
11. 日常巡检清单
| 检查项 | 方法 | 频率 |
|---|---|---|
| 慢查询分析 | pt-query-digest | 每日 |
| 死锁检查 | SHOW ENGINE INNODB STATUS | 每日 |
| 长事务监控 | information_schema.INNODB_TRX | 实时 |
| 索引使用 | schema_unused_indexes | 每周 |
| 表空间增长 | information_schema.tables | 每日 |
| 连接池使用率 | Threads_connected / max_connections | 实时 |
| Buffer Pool 命中率 | Innodb_buffer_pool_read_requests | 实时 |
| 复制延迟 | Seconds_Behind_Master | 实时 |
| 连接池 pending 线程 | HikariCP metrics | 实时 |
| 执行计划退化 | pt-query-digest 对比历史基线 | 每周 |
9. 总结
数据库性能监控的三层体系:
┌─────────────────────────────────────────────┐
│ 可视化层:PMM / Grafana Dashboard │
│ 趋势图、Top N、告警通知 │
├─────────────────────────────────────────────┤
│ 分析层:pt-query-digest / Performance Schema│
│ 慢查询、执行计划、锁等待、未使用索引 │
├─────────────────────────────────────────────┤
│ 基础层:SHOW [GLOBAL] STATUS / INNODB STATUS│
│ QPS、连接数、Buffer Pool、Redo Log、死锁 │
└─────────────────────────────────────────────┘
监控的最高境界是防患于未然:通过基线建立,在性能劣化变为故障前发现并处理。建立完善的监控与告警机制,将数据库故障从"被动救火"转变为"主动预防"。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。