概述
数据库慢了,业务就慢了。这句话每个运维都听过,但真正把数据库监控做到位的团队,说实话不多。
我见过太多团队的数据库监控长这样:Zabbix 模板跑着十年前的指标,告警只有"CPU 超过 90%“和"磁盘满了"两条,数据库内部的连接数、锁等待、缓存命中率、复制延迟——这些真正能提前预警的指标,压根没采。等业务方跑来说"接口好慢”,你才发现数据库连接池早就满了,慢查询堆积了几百条。
数据库监控的核心难点不在于"装个 exporter",而在于你知道该看哪些指标、怎么设阈值、怎么从指标变化中读出数据库的健康状况。这篇文章就把这些讲透。
覆盖 MySQL 和 PostgreSQL 两大主流数据库,从 exporter 部署、核心指标解读、告警规则设计到 Grafana 仪表盘配置,全链路打通。技术栈基于 Prometheus + Grafana,这是目前开源数据库监控的事实标准。
数据库监控的三层架构
在动手之前,先理清监控的层次。数据库监控不是一股脑把所有指标都采上来就完了,得分层:
┌──────────────────────────────────────────────────┐
│ 业务应用层 │
│ 核心接口响应时间 | 业务操作成功率 | 业务吞吐量 │
│ → 这些指标直接关联用户体验 │
├──────────────────────────────────────────────────┤
│ 数据库内核层 │
│ 连接数 | QPS/TPS | 慢查询 | 锁等待 | 缓存命中率 │
│ 复制延迟 | 死锁 | WAL写入 | 事务回滚率 │
│ → 这些指标反映数据库内部运行状态 │
├──────────────────────────────────────────────────┤
│ 服务器资源层 │
│ CPU | 内存 | 磁盘IO | 网络 | 文件系统 │
│ → 这些指标是数据库运行的物理基础 │
└──────────────────────────────────────────────────┘
参考 数据库性能监控体系构建 的三层架构设计,每一层关注的问题不同:
- 服务器资源层:CPU 用户态超 80% 可能是 SQL 执行效率低;磁盘 IOPS 接近上限会导致查询排队;内存不够会触发 swap,查询延迟可能增加 10 倍以上。这一层用 node_exporter 就能覆盖。
- 数据库内核层:这是本文的重点。连接数、QPS、缓存命中率、锁等待这些指标,node_exporter 采不到,需要专门的数据库 exporter。
- 业务应用层:最终要落到业务指标上。如果 QPS 1 万但平均响应时间 500ms,那数据库健康度就是不达标的。
MySQL 监控:mysqld_exporter
部署 mysqld_exporter
mysqld_exporter 是 Prometheus 官方维护的 MySQL 监控导出器,通过连接 MySQL 读取 SHOW GLOBAL STATUS、information_schema、performance_schema 等系统视图来采集指标。
第一步:创建监控用户
-- 创建专用监控账号,最小权限原则
CREATE USER 'mysqld_exporter'@'localhost' IDENTIFIED BY 'StrongMonitorPass123!';
-- 授予必要权限
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'mysqld_exporter'@'localhost';
-- 如果需要采集 performance_schema 中的详细指标(推荐)
GRANT SELECT ON performance_schema.* TO 'mysqld_exporter'@'localhost';
-- 刷新权限
FLUSH PRIVILEGES;
权限说明:
| 权限 | 用途 | 必需性 |
|---|---|---|
| PROCESS | 查看进程列表,采集连接信息 | 必需 |
| REPLICATION CLIENT | 查看主从复制状态 | 主从架构必需 |
| SELECT (performance_schema) | 采集性能模式指标 | 推荐 |
| SELECT (information_schema) | 采集表大小、引擎状态 | 推荐 |
第二步:安装 exporter
# 下载最新版本(截至 2026 年 7 月,v0.18.0)
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.18.0/mysqld_exporter-0.18.0.linux-amd64.tar.gz
# 解压
tar xzf mysqld_exporter-0.18.0.linux-amd64.tar.gz
mv mysqld_exporter-0.18.0.linux-amd64 /usr/local/mysqld_exporter
# 创建配置文件(存储连接信息)
cat > /usr/local/mysqld_exporter/.my.cnf << 'EOF'
[client]
user=mysqld_exporter
password=StrongMonitorPass123!
EOF
# 修改文件权限(密码安全)
chmod 600 /usr/local/mysqld_exporter/.my.cnf
第三步:创建 systemd 服务
cat > /etc/systemd/system/mysqld_exporter.service << 'EOF'
[Unit]
Description=MySQL Exporter for Prometheus
After=network.target mysql.service
[Service]
Type=simple
User=mysql
ExecStart=/usr/local/mysqld_exporter/mysqld_exporter \
--config.my-cnf=/usr/local/mysqld_exporter/.my.cnf \
--web.listen-address=:9104 \
--collect.perf_schema.eventsstatements \
--collect.perf_schema.eventswaits \
--collect.perf_schema.file_instances \
--collect.global_status \
--collect.global_variables \
--collect.info_schema.innodb_metrics \
--collect.info_schema.innodb_tablespaces \
--collect.info_schema.processlist \
--collect.info_schema.tables \
--collect.auto_increment.columns
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable mysqld_exporter
systemctl start mysqld_exporter
# 验证
curl -s http://localhost:9104/metrics | head -20
--collect 参数控制采集哪些指标组。上面的配置是我在生产环境使用的完整集合,覆盖了性能模式、InnoDB 引擎状态、进程列表、表统计等。每个 --collect 参数会增加 exporter 对 MySQL 的查询压力,如果数据库负载很高,可以先只开 global_status 和 global_variables,按需增加。
MySQL 核心监控指标
mysqld_exporter 采集几百个指标,但日常关注的核心指标就这些:
连接与并发
| 指标名称 | 含义 | 告警阈值建议 |
|---|---|---|
mysql_global_status_threads_connected | 当前连接数 | > max_connections * 80% |
mysql_global_status_threads_running | 活跃线程数 | 持续 > 50 需关注 |
mysql_global_status_max_used_connections | 历史最大连接数 | 接近 max_connections 需扩容 |
mysql_global_status_aborted_connects | 连接失败次数 | 短时间内突增需排查 |
mysql_global_status_threads_created | 创建的线程数 | 持续增长说明 thread_cache_size 太小 |
查询性能
| 指标名称 | 含义 | 关注点 |
|---|---|---|
mysql_global_status_questions | 总查询数 | 用于计算 QPS |
mysql_global_status_slow_queries | 慢查询数 | 持续增长需要排查慢 SQL |
mysql_global_status_com_select | SELECT 次数 | 区分读写比例 |
mysql_global_status_com_insert | INSERT 次数 | — |
mysql_global_status_com_update | UPDATE 次数 | — |
InnoDB 引擎
| 指标名称 | 含义 | 关注点 |
|---|---|---|
mysql_global_status_innodb_buffer_pool_pages_free | 空闲缓冲页 | 太少说明内存不足 |
mysql_global_status_innodb_buffer_pool_read_requests | 缓冲池读请求 | 与 physical reads 计算命中率 |
mysql_global_status_innodb_buffer_pool_reads | 物理磁盘读 | 缓冲池未命中时的磁盘读 |
mysql_global_status_innodb_row_lock_waits | 行锁等待次数 | 突增说明锁竞争严重 |
mysql_global_status_innodb_row_lock_time_avg | 平均锁等待时间 | > 50ms 需要关注 |
复制状态
| 指标名称 | 含义 | 告警阈值 |
|---|---|---|
mysql_slave_status_slave_io_running | IO 线程状态 | != 1 则告警 |
mysql_slave_status_slave_sql_running | SQL 线程状态 | != 1 则告警 |
mysql_slave_status_seconds_behind_master | 主从延迟秒数 | > 60 需要告警 |
关键 PromQL 查询
# QPS(每秒查询数)
rate(mysql_global_status_questions[1m])
# 慢查询增长率
rate(mysql_global_status_slow_queries[5m])
# 连接使用率
mysql_global_status_threads_connected / mysql_global_variables_max_connections * 100
# InnoDB 缓冲池命中率
1 - (rate(mysql_global_status_innodb_buffer_pool_reads[5m]) /
rate(mysql_global_status_innodb_buffer_pool_read_requests[5m]))
# 行锁等待次数(每分钟)
rate(mysql_global_status_innodb_row_lock_waits[1m])
# 主从复制延迟
mysql_slave_status_seconds_behind_master
# 运行线程数
mysql_global_status_threads_running
缓冲池命中率是我最关注的指标之一。低于 99% 说明 InnoDB 缓冲池内存不够,频繁从磁盘读数据,查询延迟会显著上升。如果低于 95%,该加内存了。
PostgreSQL 监控:postgres_exporter
部署 postgres_exporter
postgres_exporter 同样是 Prometheus 社区维护的 PostgreSQL 监控导出器,通过连接 PostgreSQL 执行 pg_stat_* 系列视图查询来采集指标。
第一步:创建监控用户
-- 创建监控用户
CREATE USER monitor WITH PASSWORD 'StrongMonitorPass123!';
-- 设置搜索路径
ALTER USER monitor SET search_path TO monitor, pg_catalog;
-- PostgreSQL 10+ 使用 pg_monitor 角色(推荐)
GRANT pg_monitor TO monitor;
-- 如果需要 pg_stat_statements 扩展(查询性能分析)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
GRANT SELECT ON pg_stat_statements TO monitor;
-- 如果需要自定义查询
CREATE SCHEMA IF NOT EXISTS monitor AUTHORIZATION monitor;
pg_monitor 角色是 PostgreSQL 10 引入的,它包含了一组只读访问统计视图的权限。用这个角色比手动逐个 GRANT 方便得多,也是官方推荐的做法。参考 postgres_exporter 部署实践,这个方案安全可控,不需要超级用户权限。
第二步:安装 exporter
# 下载
wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.15.0/postgres_exporter-0.15.0.linux-amd64.tar.gz
tar xzf postgres_exporter-0.15.0.linux-amd64.tar.gz
mv postgres_exporter-0.15.0.linux-amd64 /usr/local/postgres_exporter
# 通过环境变量传递连接信息
cat > /usr/local/postgres_exporter/postgres_exporter.env << 'EOF'
DATA_SOURCE_NAME=postgresql://monitor:StrongMonitorPass123!@localhost:5432/postgres?sslmode=disable
EOF
第三步:创建 systemd 服务
cat > /etc/systemd/system/postgres_exporter.service << 'EOF'
[Unit]
Description=PostgreSQL Exporter for Prometheus
After=network.target postgresql.service
[Service]
Type=simple
User=postgres
EnvironmentFile=/usr/local/postgres_exporter/postgres_exporter.env
ExecStart=/usr/local/postgres_exporter/postgres_exporter \
--web.listen-address=:9187 \
--extend.query-path=/usr/local/postgres_exporter/queries.yaml \
--auto-discover-databases
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable postgres_exporter
systemctl start postgres_exporter
# 验证
curl -s http://localhost:9187/metrics | head -20
--auto-discover-databases 参数让 exporter 自动发现并监控所有数据库,不用逐个配置。--extend.query-path 指向自定义查询配置文件,可以添加 exporter 默认不采集的指标。
自定义查询配置
postgres_exporter 支持通过 YAML 文件扩展采集指标,这是它比 mysqld_exporter 灵活的地方。你可以写自己的 SQL 查询,把业务相关的指标也导出来:
# /usr/local/postgres_exporter/queries.yaml
# 表膨胀检测
pg_table_bloat:
query: >
SELECT
schemaname AS schema,
relname AS table_name,
n_live_tup AS live_tuples,
n_dead_tup AS dead_tuples,
ROUND(n_dead_tup::FLOAT / GREATEST(n_live_tup, 1) * 100, 2) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_live_tup > 1000
ORDER BY dead_ratio DESC
LIMIT 20
metrics:
- schema:
usage: "LABEL"
description: "Schema name"
- table_name:
usage: "LABEL"
description: "Table name"
- live_tuples:
usage: "GAUGE"
description: "Number of live tuples"
- dead_tuples:
usage: "GAUGE"
description: "Number of dead tuples"
- dead_ratio:
usage: "GAUGE"
description: "Dead tuple ratio percentage"
# 长事务检测
pg_long_running_transactions:
query: >
SELECT
pid,
extract(EPOCH FROM (now() - xact_start)) AS duration_seconds,
state,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
AND now() - xact_start > interval '5 minutes'
metrics:
- pid:
usage: "LABEL"
description: "Process ID"
- duration_seconds:
usage: "GAUGE"
description: "Transaction duration in seconds"
- state:
usage: "LABEL"
description: "Transaction state"
- query:
usage: "LABEL"
description: "Query text"
# 连接池使用情况
pg_connection_pool:
query: >
SELECT
datname AS database,
count(*) AS total_connections,
count(*) FILTER (WHERE state = 'active') AS active_connections,
count(*) FILTER (WHERE state = 'idle') AS idle_connections,
count(*) FILTER (WHERE state = 'idle in transaction') AS idle_in_transaction
FROM pg_stat_activity
GROUP BY datname
metrics:
- database:
usage: "LABEL"
description: "Database name"
- total_connections:
usage: "GAUGE"
description: "Total connections"
- active_connections:
usage: "GAUGE"
description: "Active connections"
- idle_connections:
usage: "GAUGE"
description: "Idle connections"
- idle_in_transaction:
usage: "GAUGE"
description: "Idle in transaction connections"
PostgreSQL 核心监控指标
连接与事务
| 指标名称 | 含义 | 关注点 |
|---|---|---|
pg_stat_database_numbackends | 当前连接数 | 接近 max_connections 需扩容 |
pg_stat_database_xact_commit | 已提交事务数 | 计算 TPS |
pg_stat_database_xact_rollback | 回滚事务数 | 回滚率高说明有问题 |
pg_stat_database_blks_hit | 缓冲命中数 | 计算缓存命中率 |
pg_stat_database_blks_read | 磁盘读取数 | 与 blks_hit 计算命中率 |
pg_stat_database_deadlocks | 死锁次数 | 应为 0,有就排查 |
复制状态
| 指标名称 | 含义 | 告警阈值 |
|---|---|---|
pg_stat_replication_pg_wal_lsn_diff | WAL 延迟字节数 | > 1GB 需要关注 |
pg_replication_lag | 复制延迟秒数 | > 60 需要告警 |
锁与等待
| 指标名称 | 含义 | 关注点 |
|---|---|---|
pg_locks_count | 锁数量 | 突增需要排查 |
pg_stat_activity_count | 活动会话数 | 持续增长需关注 |
pg_stat_activity_max_tx_duration | 最长事务持续时间 | > 300 秒需要告警 |
关键 PromQL 查询
# TPS(每秒事务数)
rate(pg_stat_database_xact_commit[1m]) + rate(pg_stat_database_xact_rollback[1m])
# 事务回滚率
rate(pg_stat_database_xact_rollback[5m]) /
(rate(pg_stat_database_xact_commit[5m]) + rate(pg_stat_database_xact_rollback[5m])) * 100
# 缓存命中率
rate(pg_stat_database_blks_hit[5m]) /
(rate(pg_stat_database_blks_hit[5m]) + rate(pg_stat_database_blks_read[5m])) * 100
# 活动连接数
pg_stat_database_numbackends
# 复制延迟(字节)
pg_stat_replication_pg_wal_lsn_diff
# 死锁累计次数
pg_stat_database_deadlocks
Prometheus 采集配置
两个 exporter 部署好之后,需要在 Prometheus 中添加采集任务:
# prometheus.yml
scrape_configs:
# MySQL 监控
- job_name: 'mysql'
scrape_interval: 15s
static_configs:
- targets: ['mysql-prod-01:9104', 'mysql-prod-02:9104']
labels:
env: 'production'
cluster: 'mysql-prod'
- targets: ['mysql-test-01:9104']
labels:
env: 'staging'
cluster: 'mysql-test'
# PostgreSQL 监控
- job_name: 'postgresql'
scrape_interval: 15s
static_configs:
- targets: ['pg-prod-01:9187', 'pg-prod-02:9187']
labels:
env: 'production'
cluster: 'pg-prod'
- targets: ['pg-test-01:9187']
labels:
env: 'staging'
cluster: 'pg-test'
采集间隔 15 秒是合理的默认值。如果你需要更精细的监控(比如捕获短时尖峰),可以设为 10 秒,但要注意这会增加 Prometheus 的存储压力和 exporter 对数据库的查询负载。5 秒以下不建议,除非你明确知道自己在做什么。
对于大规模数据库集群,推荐使用服务发现替代静态配置:
scrape_configs:
# 使用 Consul 服务发现
- job_name: 'mysql-consul'
consul_sd_configs:
- server: 'consul.internal:8500'
services: ['mysqld-exporter']
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: '.*,production,.*'
target_label: env
replacement: 'production'
# 使用文件服务发现(适合 VM 环境)
- job_name: 'postgresql-file'
file_sd_configs:
- files: ['/etc/prometheus/targets/pg-*.yml']
refresh_interval: 30s
文件服务发现示例:
# /etc/prometheus/targets/pg-prod.yml
- targets:
- 10.0.1.10:9187
- 10.0.1.11:9187
labels:
env: production
cluster: pg-prod
role: primary
告警规则设计
监控不是目的,告警才是。但告警设计是数据库监控中最容易做烂的一环——告警太多,大家就麻木了;告警太少,问题漏掉了。
告警分级原则
参考 Google SRE Book 的告警哲学,我把数据库告警分为三级:
| 级别 | 含义 | 响应要求 | 示例 |
|---|---|---|---|
| P0 - 紧急 | 数据库不可用或即将不可用 | 立即响应,任何时间 | 主库宕机、复制中断、磁盘满 |
| P1 - 警告 | 性能严重下降,影响业务 | 工作时间内响应 | 连接数超 80%、复制延迟超 60 秒 |
| P2 - 提示 | 潜在风险,暂不影响业务 | 日常巡检处理 | 缓存命中率下降、慢查询增加 |
MySQL 告警规则
# /etc/prometheus/rules/mysql-alerts.yml
groups:
- name: mysql-alerts
rules:
# ===== P0 级别 =====
# MySQL 实例宕机
- alert: MySQLDown
expr: mysql_up == 0
for: 1m
labels:
severity: P0
annotations:
summary: "MySQL 实例宕机 {{ $labels.instance }}"
description: "MySQL 实例 {{ $labels.instance }} 已离线超过 1 分钟"
# 主从复制 IO 线程停止
- alert: MySQLReplicationIOStopped
expr: mysql_slave_status_slave_io_running == 0
for: 1m
labels:
severity: P0
annotations:
summary: "MySQL 复制 IO 线程停止 {{ $labels.instance }}"
description: "从库 {{ $labels.instance }} 的 IO 线程已停止"
# 主从复制 SQL 线程停止
- alert: MySQLReplicationSQLStopped
expr: mysql_slave_status_slave_sql_running == 0
for: 1m
labels:
severity: P0
annotations:
summary: "MySQL 复制 SQL 线程停止 {{ $labels.instance }}"
description: "从库 {{ $labels.instance }} 的 SQL 线程已停止"
# ===== P1 级别 =====
# 连接数过高
- alert: MySQLHighConnections
expr: >
mysql_global_status_threads_connected /
mysql_global_variables_max_connections * 100 > 80
for: 5m
labels:
severity: P1
annotations:
summary: "MySQL 连接数过高 {{ $labels.instance }}"
description: "连接使用率 {{ printf \"%.1f\" $value }}%,已超过 80% 持续 5 分钟"
# 主从复制延迟过大
- alert: MySQLReplicationLag
expr: mysql_slave_status_seconds_behind_master > 60
for: 5m
labels:
severity: P1
annotations:
summary: "MySQL 复制延迟 {{ $labels.instance }}"
description: "从库 {{ $labels.instance }} 延迟 {{ $value }} 秒"
# 慢查询突增
- alert: MySQLSlowQueries
expr: rate(mysql_global_status_slow_queries[5m]) > 5
for: 10m
labels:
severity: P1
annotations:
summary: "MySQL 慢查询突增 {{ $labels.instance }}"
description: "慢查询速率 {{ printf \"%.1f\" $value }} 次/秒"
# InnoDB 行锁等待过多
- alert: MySQLRowLockWaits
expr: rate(mysql_global_status_innodb_row_lock_waits[5m]) > 10
for: 5m
labels:
severity: P1
annotations:
summary: "MySQL 行锁等待过多 {{ $labels.instance }}"
description: "行锁等待 {{ printf \"%.1f\" $value }} 次/秒"
# ===== P2 级别 =====
# InnoDB 缓冲池命中率下降
- alert: MySQLLowBufferPoolHitRate
expr: >
1 - (rate(mysql_global_status_innodb_buffer_pool_reads[5m]) /
rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])) < 0.99
for: 15m
labels:
severity: P2
annotations:
summary: "MySQL 缓冲池命中率低 {{ $labels.instance }}"
description: "缓冲池命中率 {{ printf \"%.2f\" (1 - $value) }}%,低于 99%"
# 连接失败次数增加
- alert: MySQLAbortedConnects
expr: rate(mysql_global_status_aborted_connects[5m]) > 10
for: 5m
labels:
severity: P2
annotations:
summary: "MySQL 连接失败增加 {{ $labels.instance }}"
description: "连接失败 {{ printf \"%.1f\" $value }} 次/秒"
PostgreSQL 告警规则
# /etc/prometheus/rules/postgresql-alerts.yml
groups:
- name: postgresql-alerts
rules:
# ===== P0 级别 =====
# PostgreSQL 实例宕机
- alert: PostgreSQLDown
expr: pg_up == 0
for: 1m
labels:
severity: P0
annotations:
summary: "PostgreSQL 实例宕机 {{ $labels.instance }}"
description: "PostgreSQL 实例 {{ $labels.instance }} 已离线"
# ===== P1 级别 =====
# 连接数过高
- alert: PostgreSQLHighConnections
expr: >
pg_stat_database_numbackends /
pg_settings_max_connections * 100 > 80
for: 5m
labels:
severity: P1
annotations:
summary: "PostgreSQL 连接数过高 {{ $labels.instance }}"
description: "连接使用率 {{ printf \"%.1f\" $value }}%"
# 复制延迟过大
- alert: PostgreSQLReplicationLag
expr: pg_stat_replication_pg_wal_lsn_diff > 1073741824
# 1073741824 bytes = 1GB
for: 5m
labels:
severity: P1
annotations:
summary: "PostgreSQL 复制延迟 {{ $labels.instance }}"
description: "WAL 延迟 {{ printf \"%.2f\" ($value / 1073741824) }} GB"
# 死锁发生
- alert: PostgreSQLDeadlocks
expr: rate(pg_stat_database_deadlocks[5m]) > 0
for: 1m
labels:
severity: P1
annotations:
summary: "PostgreSQL 死锁 {{ $labels.instance }}"
description: "检测到死锁,速率 {{ $value }} 次/秒"
# 事务回滚率过高
- alert: PostgreSQLHighRollbackRate
expr: >
rate(pg_stat_database_xact_rollback[5m]) /
(rate(pg_stat_database_xact_commit[5m]) +
rate(pg_stat_database_xact_rollback[5m])) * 100 > 10
for: 10m
labels:
severity: P1
annotations:
summary: "PostgreSQL 回滚率过高 {{ $labels.instance }}"
description: "事务回滚率 {{ printf \"%.1f\" $value }}%"
# ===== P2 级别 =====
# 缓存命中率下降
- alert: PostgreSQLLowCacheHitRate
expr: >
rate(pg_stat_database_blks_hit[5m]) /
(rate(pg_stat_database_blks_hit[5m]) +
rate(pg_stat_database_blks_read[5m])) * 100 < 95
for: 15m
labels:
severity: P2
annotations:
summary: "PostgreSQL 缓存命中率低 {{ $labels.instance }}"
description: "缓存命中率 {{ printf \"%.1f\" $value }}%,低于 95%"
告警治理:减少告警噪声
告警太多比告警太少更危险——当你的手机一小时响了 30 次,你会开始忽略所有告警。以下几个实践能有效减少告警噪声:
1. 用 for 持续时间过滤瞬时抖动
# 错误做法:连接数瞬间超 80% 就告警
expr: connection_usage > 80
for: 0m
# 正确做法:持续超 80% 5 分钟才告警
expr: connection_usage > 80
for: 5m
数据库连接数在业务高峰期短暂超 80% 是正常的,只有持续高位才说明有问题。
2. 区分主从角色,只对主库的关键指标告警
# 只对主库的复制状态告警
expr: >
mysql_slave_status_slave_io_running == 0
and on(instance) mysql_master_status == 1
从库复制中断确实需要告警,但如果你的架构是多源复制或者从库是只读节点,告警级别可以降低。
3. 使用抑制规则避免告警风暴
# Alertmanager 配置:当 MySQL 实例宕机时,抑制该实例的所有其他告警
inhibit_rules:
- source_match:
severity: P0
alertname: MySQLDown
target_match_re:
severity: P1|P2
equal: ['instance']
实例都宕机了,再告"连接数高"、“慢查询多"没有意义,反而干扰排查。
Grafana 仪表盘配置
推荐的社区仪表盘
不用从零开始画仪表盘,Grafana 社区有大量现成的数据库监控面板:
MySQL 仪表盘:
- Grafana Dashboard ID:
7362— MySQL Overview(最常用,涵盖连接、查询、InnoDB、复制) - Grafana Dashboard ID:
6239— MySQL Replication(专注主从复制监控) - Grafana Dashboard ID:
9645— MySQL InnoDB Metrics(InnoDB 引擎详情)
PostgreSQL 仪表盘:
- Grafana Dashboard ID:
9628— PostgreSQL Database(通用面板) - Grafana Dashboard ID:
17026— PostgreSQL Details(详细指标) - Grafana Dashboard ID:
12485— pg_stat_statements(查询性能分析)
导入方法:Grafana → Dashboards → Import → 输入 Dashboard ID → Load。
自建仪表盘的关键面板
社区仪表盘虽然方便,但每个团队的业务场景不同。我建议在社区仪表盘基础上,增加几个与业务相关的面板:
面板1:数据库连接趋势(多实例对比)
# Panel: 连接数趋势
mysql_global_status_threads_connected{cluster=~"$cluster"}
设置 $cluster 变量,可以按集群筛选。用 Time series 图表类型,多个实例叠加对比,一眼看出哪个实例连接数异常。
面板2:QPS / TPS 对比
# Panel: QPS
rate(mysql_global_status_questions[1m])
# Panel: TPS
rate(mysql_global_status_com_insert[1m]) +
rate(mysql_global_status_com_update[1m]) +
rate(mysql_global_status_com_delete[1m])
把 QPS 和 TPS 放在一个面板里对比,可以判断数据库是读密集型还是写密集型。
面板3:缓存命中率仪表盘
# Panel: InnoDB 缓冲池命中率
(1 - rate(mysql_global_status_innodb_buffer_pool_reads[5m]) /
rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])) * 100
用 Gauge 图表类型,阈值设为 95% 黄色、99% 绿色,一目了然。
面板4:慢查询 TOP 10(表格)
# Panel: 慢查询排行(需要 performance_schema)
topk(10,
sum by (digest_text) (
rate(mysql_perf_schema_events_statements_sum_total_timer_wait[5m])
)
)
用 Table 图表类型,展示最慢的 10 条 SQL,方便快速定位性能瓶颈。
面板5:主从复制延迟
# Panel: 复制延迟
mysql_slave_status_seconds_behind_master{cluster=~"$cluster"}
用 Time series 图表类型,所有从库的延迟趋势叠加,异常的从库会明显偏高。
监控方案对比与选型
Prometheus 方案 vs 商业方案
除了 Prometheus + exporter 的自建方案,市面上还有不少商业数据库监控工具。参考 MySQL 监控工具选型对比,主流方案对比如下:
| 维度 | Prometheus + Exporter | Percona PMM | Datadog | 云厂商监控 |
|---|---|---|---|---|
| 成本 | 免费 | 免费版可用 | 按主机付费 | 按实例付费 |
| 部署难度 | 中等 | 低 | 低 | 极低 |
| 自定义能力 | 极强 | 强 | 中等 | 弱 |
| MySQL 支持 | 好 | 极好(Percona 是 MySQL 分支维护者) | 好 | 一般 |
| PostgreSQL 支持 | 好 | 一般 | 好 | 一般 |
| 告警能力 | Alertmanager,需手动配置 | 内置 | 内置 | 内置 |
| 长期存储 | 需配 Thanos/VictoriaMetrics | 内置 | 内置 | 内置 |
| 适用规模 | 中小到大型 | 中小型 | 中大型 | 小型 |
我的选型建议:
- 小团队 / 初创期:云厂商自带的数据库监控就够用了,别折腾。
- 中等规模 / 多数据库混合:Prometheus + exporter,灵活度和成本控制都好。
- MySQL 为主 / 需要深度分析:Percona PMM 值得一试,Query Analytics 功能很强。
- 预算充足 / 不想维护:Datadog,开箱即用,告警和可视化都做得好。
PostgreSQL 专属方案:pgwatch3
对于以 PostgreSQL 为主的团队,除了 postgres_exporter,还有一个值得关注的工具:pgwatch3。参考 pgwatch3 PostgreSQL 监控方案,它是 CYBERTEC 公司用 Go 语言开发的 PostgreSQL 专用监控工具,预置数十套 Grafana 仪表盘,自定义指标直接写 SQL,部署只要一行 Docker 命令。
| 方案 | 部署时间 | 自带仪表盘 | 自定义指标 | 学习曲线 |
|---|---|---|---|---|
| Prometheus + postgres_exporter | 0.5-1 天 | 需导入社区 ID | 改 SQL 改 exporter | 中等 |
| pgwatch3 | 10 分钟 | 数十套 | 直接写 SQL | 平缓 |
| Zabbix 模板 | 1-2 天 | 需自配 | 写脚本 | 陡峭 |
如果你的环境是纯 PostgreSQL 且不想花时间搭 Grafana 仪表盘,pgwatch3 是更高效的选择。但如果你的监控体系已经基于 Prometheus,postgres_exporter 更容易集成。
生产环境部署清单
#!/bin/bash
# 数据库监控一键部署脚本(Prometheus + Exporter)
# 适用于 RHEL/CentOS 8+,MySQL + PostgreSQL 环境
set -euo pipefail
# ===== 配置区 =====
MYSQL_EXPORTER_VERSION="0.18.0"
PG_EXPORTER_VERSION="0.15.0"
EXPORTER_USER="monitor"
EXPORTER_GROUP="monitor"
# ===== 创建用户 =====
id -u $EXPORTER_USER &>/dev/null || useradd -r -s /sbin/nologin $EXPORTER_USER
# ===== MySQL Exporter =====
echo "=== Installing mysqld_exporter ==="
cd /tmp
wget -q "https://github.com/prometheus/mysqld_exporter/releases/download/v${MYSQL_EXPORTER_VERSION}/mysqld_exporter-${MYSQL_EXPORTER_VERSION}.linux-amd64.tar.gz"
tar xzf "mysqld_exporter-${MYSQL_EXPORTER_VERSION}.linux-amd64.tar.gz"
mv "mysqld_exporter-${MYSQL_EXPORTER_VERSION}.linux-amd64" /usr/local/mysqld_exporter
chown -R $EXPORTER_USER:$EXPORTER_GROUP /usr/local/mysqld_exporter
# 创建配置文件模板
cat > /usr/local/mysqld_exporter/.my.cnf << 'EOF'
[client]
user=mysqld_exporter
password=CHANGE_ME_STRONG_PASSWORD
EOF
chmod 600 /usr/local/mysqld_exporter/.my.cnf
# systemd 服务
cat > /etc/systemd/system/mysqld_exporter.service << EOF
[Unit]
Description=MySQL Exporter
After=network.target
[Service]
Type=simple
User=$EXPORTER_USER
ExecStart=/usr/local/mysqld_exporter/mysqld_exporter \\
--config.my-cnf=/usr/local/mysqld_exporter/.my.cnf \\
--web.listen-address=:9104 \\
--collect.global_status \\
--collect.global_variables \\
--collect.perf_schema.eventsstatements \\
--collect.info_schema.innodb_metrics \\
--collect.info_schema.processlist
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# ===== PostgreSQL Exporter =====
echo "=== Installing postgres_exporter ==="
wget -q "https://github.com/prometheus-community/postgres_exporter/releases/download/v${PG_EXPORTER_VERSION}/postgres_exporter-${PG_EXPORTER_VERSION}.linux-amd64.tar.gz"
tar xzf "postgres_exporter-${PG_EXPORTER_VERSION}.linux-amd64.tar.gz"
mv "postgres_exporter-${PG_EXPORTER_VERSION}.linux-amd64" /usr/local/postgres_exporter
chown -R $EXPORTER_USER:$EXPORTER_GROUP /usr/local/postgres_exporter
# 环境变量模板
cat > /usr/local/postgres_exporter/postgres_exporter.env << 'EOF'
DATA_SOURCE_NAME=postgresql://monitor:CHANGE_ME_STRONG_PASSWORD@localhost:5432/postgres?sslmode=disable
EOF
chmod 600 /usr/local/postgres_exporter/postgres_exporter.env
# systemd 服务
cat > /etc/systemd/system/postgres_exporter.service << EOF
[Unit]
Description=PostgreSQL Exporter
After=network.target
[Service]
Type=simple
User=$EXPORTER_USER
EnvironmentFile=/usr/local/postgres_exporter/postgres_exporter.env
ExecStart=/usr/local/postgres_exporter/postgres_exporter \\
--web.listen-address=:9187 \\
--auto-discover-databases
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# ===== 启动服务 =====
systemctl daemon-reload
systemctl enable mysqld_exporter postgres_exporter
systemctl start mysqld_exporter postgres_exporter
# ===== 验证 =====
echo "=== Verification ==="
sleep 2
echo "MySQL Exporter:"
curl -s http://localhost:9104/metrics | grep mysql_up | head -1
echo "PostgreSQL Exporter:"
curl -s http://localhost:9187/metrics | grep pg_up | head -1
echo ""
echo "=== Next Steps ==="
echo "1. Edit /usr/local/mysqld_exporter/.my.cnf with real MySQL credentials"
echo "2. Edit /usr/local/postgres_exporter/postgres_exporter.env with real PG credentials"
echo "3. Add scrape configs to prometheus.yml"
echo "4. Import Grafana dashboards: MySQL=7362, PostgreSQL=9628"
echo "5. Deploy alert rules to Prometheus"
安全注意事项
数据库监控涉及敏感信息,安全方面不能马虎:
监控账号最小权限:只给 PROCESS、REPLICATION CLIENT、SELECT 权限,不给写入权限,不给 SUPER 权限。
exporter 配置文件权限:
.my.cnf和postgres_exporter.env包含明文密码,必须chmod 600,属主为运行 exporter 的用户。端口访问控制:exporter 的 9104/9187 端口暴露了数据库运行状态信息,不应公网可访问。用防火墙限制只允许 Prometheus 服务器访问:
# iptables 规则
iptables -A INPUT -p tcp --dport 9104 -s 10.0.1.100 -j ACCEPT # Prometheus IP
iptables -A INPUT -p tcp --dport 9104 -j DROP
iptables -A INPUT -p tcp --dport 9187 -s 10.0.1.100 -j ACCEPT
iptables -A INPUT -p tcp --dport 9187 -j DROP
- TLS 加密:如果 exporter 和 Prometheus 之间走公网,启用 TLS:
# exporter 启动参数
--web.config.file=/usr/local/mysqld_exporter/web.yml
# web.yml
tls_server_config:
cert_file: /etc/ssl/exporter.crt
key_file: /etc/ssl/exporter.key
- 不要在告警 annotation 中暴露敏感信息:告警会发到飞书/钉钉等群,别在描述里写数据库密码或连接串。
总结
数据库监控做得好不好,直接决定了你排查问题的速度。几个实战经验:
三层监控缺一不可。服务器资源层用 node_exporter,数据库内核层用 mysqld_exporter / postgres_exporter,业务应用层用 APM 或自定义指标。只做一层等于瘸腿。
核心指标就那么几个。连接数、QPS/TPS、缓存命中率、慢查询、复制延迟、锁等待——抓住这六个,数据库健康度就有底了。别被几百个指标吓到,大部分你用不上。
告警分级是关键。P0 立即响应,P1 工作时间响应,P2 巡检处理。不分级的告警等于没有告警——全都是高优先级,最后大家都忽略。
缓存命中率是最被忽视的指标。MySQL InnoDB 缓冲池命中率低于 99%、PostgreSQL 缓存命中率低于 95%,就该加内存了。等低于 90% 业务已经很慢了。
for持续时间是减少误报的关键。连接数瞬间超 80% 不用告警,持续 5 分钟才说明有问题。把for设对了,告警噪声能减少 60% 以上。自定义查询是 PostgreSQL 监控的杀手锏。postgres_exporter 的
queries.yaml让你用 SQL 定义指标,表膨胀率、长事务、连接池状态——想采什么采什么。MySQL 的 mysqld_exporter 就没这么灵活。安全不能忘。监控账号最小权限,配置文件 chmod 600,端口防火墙限制。数据库监控本身泄露密码的事故,我见过不止一次。
最后一句话:数据库是业务的心脏,监控就是心电图。心电图不装好,心脏出问题了你都不知道。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- MySQL 监控体系搭建:常用监控工具、核心监控指标 — CSDN,MySQL 监控工具选型对比与核心指标体系说明
- 数据库性能监控与调优实战指南 — Book118,数据库性能监控三层架构设计与核心指标定义
- Prometheus 监控 PostgreSQL 深度实战 — CSDN,postgres_exporter 部署实践与权限配置方案
- 手把手搭建基于 Prometheus + Grafana 的高效数据库监控体系 — 腾讯云开发者社区,数据库监控平台架构设计与部署指南
- 放弃 Prometheus?pgwatch3 把 PostgreSQL 监控干到了 GitHub 第一 — 腾讯云开发者社区,pgwatch3 与 PostgreSQL 监控方案对比分析
- Monitoring Distributed Systems — Google SRE Book — Google SRE Team,告警哲学与监控白纸黑字原则