数据库性能监控与诊断

系统讲解数据库性能监控体系:MySQL Performance Schema / Sys Schema、慢查询分析 pt-query-digest、InnoDB Metrics、锁诊断、连接池监控与 PMM/VictoriaMetrics 可视化方案。

数据库是系统核心组件,其性能直接影响用户体验。建立完善的监控与诊断体系,是 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:
-- ...

死锁解决:

  1. 调整事务顺序(按固定顺序访问资源)
  2. 缩短事务时长
  3. 降低隔离级别
  4. 重试逻辑:ON DUPLICATE KEY UPDATE 或应用层重试

5. InnoDB 内部状态诊断

# InnoDB 完整状态
SHOW ENGINE INNODB STATUS\G

# 关键段分析

关键监控项:

段落内容健康指标
SEMAPHORES信号量争用rw-shared spins < 1000/s
TRANSACTIONS活跃事务、锁信息长事务 < 60s
FILE I/OIO 线程状态pending_reads/pending_writes 低
INSERT BUFFER AND ADAPTIVE HASH INDEXChange Buffer/AHIhash searches/s 高比例
LOGRedo Log 状态Log sequence 增长平稳
BUFFER POOL AND MEMORYBuffer Pool 使用hit rate > 95%
INDIVIDUAL BUFFER POOL INFO多实例 Buffer PoolFree 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)用于对比不同执行计划

执行计划优化路径:

  1. 检查 type = ALL → 为 WHERE/ORDER BY 列添加索引
  2. 检查 Extra = Using filesort → 建立包含 ORDER BY 字段的联合索引
  3. 检查 Extra = Using temporary → 优化 GROUP BY / 子查询,避免临时表
  4. 检查 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 STATUSSlave_IO_Running/Slave_SQL_Running ≠ Yes
读流量分布代理层(ShardingSphere/MyCat)指标读节点负载不均
主库写压力Com_insert + Com_update + Com_deleteQPS 接近单节点上限

主从延迟常见原因:

  • 从库磁盘 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、死锁  │
└─────────────────────────────────────────────┘

监控的最高境界是防患于未然:通过基线建立,在性能劣化变为故障前发现并处理。建立完善的监控与告警机制,将数据库故障从"被动救火"转变为"主动预防"。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章

  1. PostgreSQL 锁与阻塞分析
  2. COPY 与批量数据加载优化
  3. pgvector 向量检索与混合查询