概述

数据库慢了,业务就慢了。这句话每个运维都听过,但真正把数据库监控做到位的团队,说实话不多。

我见过太多团队的数据库监控长这样: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 STATUSinformation_schemaperformance_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_statusglobal_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_selectSELECT 次数区分读写比例
mysql_global_status_com_insertINSERT 次数
mysql_global_status_com_updateUPDATE 次数

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_runningIO 线程状态!= 1 则告警
mysql_slave_status_slave_sql_runningSQL 线程状态!= 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_diffWAL 延迟字节数> 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 + ExporterPercona PMMDatadog云厂商监控
成本免费免费版可用按主机付费按实例付费
部署难度中等极低
自定义能力极强中等
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_exporter0.5-1 天需导入社区 ID改 SQL 改 exporter中等
pgwatch310 分钟数十套直接写 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"

安全注意事项

数据库监控涉及敏感信息,安全方面不能马虎:

  1. 监控账号最小权限:只给 PROCESS、REPLICATION CLIENT、SELECT 权限,不给写入权限,不给 SUPER 权限。

  2. exporter 配置文件权限.my.cnfpostgres_exporter.env 包含明文密码,必须 chmod 600,属主为运行 exporter 的用户。

  3. 端口访问控制: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
  1. 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
  1. 不要在告警 annotation 中暴露敏感信息:告警会发到飞书/钉钉等群,别在描述里写数据库密码或连接串。

总结

数据库监控做得好不好,直接决定了你排查问题的速度。几个实战经验:

  1. 三层监控缺一不可。服务器资源层用 node_exporter,数据库内核层用 mysqld_exporter / postgres_exporter,业务应用层用 APM 或自定义指标。只做一层等于瘸腿。

  2. 核心指标就那么几个。连接数、QPS/TPS、缓存命中率、慢查询、复制延迟、锁等待——抓住这六个,数据库健康度就有底了。别被几百个指标吓到,大部分你用不上。

  3. 告警分级是关键。P0 立即响应,P1 工作时间响应,P2 巡检处理。不分级的告警等于没有告警——全都是高优先级,最后大家都忽略。

  4. 缓存命中率是最被忽视的指标。MySQL InnoDB 缓冲池命中率低于 99%、PostgreSQL 缓存命中率低于 95%,就该加内存了。等低于 90% 业务已经很慢了。

  5. for 持续时间是减少误报的关键。连接数瞬间超 80% 不用告警,持续 5 分钟才说明有问题。把 for 设对了,告警噪声能减少 60% 以上。

  6. 自定义查询是 PostgreSQL 监控的杀手锏。postgres_exporter 的 queries.yaml 让你用 SQL 定义指标,表膨胀率、长事务、连接池状态——想采什么采什么。MySQL 的 mysqld_exporter 就没这么灵活。

  7. 安全不能忘。监控账号最小权限,配置文件 chmod 600,端口防火墙限制。数据库监控本身泄露密码的事故,我见过不止一次。

最后一句话:数据库是业务的心脏,监控就是心电图。心电图不装好,心脏出问题了你都不知道。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. MySQL 监控体系搭建:常用监控工具、核心监控指标 — CSDN,MySQL 监控工具选型对比与核心指标体系说明
  2. 数据库性能监控与调优实战指南 — Book118,数据库性能监控三层架构设计与核心指标定义
  3. Prometheus 监控 PostgreSQL 深度实战 — CSDN,postgres_exporter 部署实践与权限配置方案
  4. 手把手搭建基于 Prometheus + Grafana 的高效数据库监控体系 — 腾讯云开发者社区,数据库监控平台架构设计与部署指南
  5. 放弃 Prometheus?pgwatch3 把 PostgreSQL 监控干到了 GitHub 第一 — 腾讯云开发者社区,pgwatch3 与 PostgreSQL 监控方案对比分析
  6. Monitoring Distributed Systems — Google SRE Book — Google SRE Team,告警哲学与监控白纸黑字原则