先说一个真实场景。我前几年接手过一批MySQL实例,经常是半夜收到业务方消息“数据库卡了”,爬起来第一件事就是查SHOW PROCESSLIST,翻慢查询日志,看磁盘空间,来回折腾二三十分钟才能拼出一个大概的故障图谱。更难受的是,很多时候问题从下午就开始了,但没人发现,直到业务反馈才暴露,中间那段时间的数据完全空白。后来我把Prometheus加Grafana这套组合搭起来,把所有MySQL实例的连接数、慢查询量、主从延迟、InnoDB缓冲池命中率、磁盘空间全部放到一个面板上,再配几条关键的告警规则,才发现“监控到位”这件事对数据库稳定性的提升有多明显——大部分问题能在业务感知之前被自动发现,至少能提前几分钟到几十分钟去处理。
这篇文章我把整个搭建过程、选型思路、踩过的坑、以及一些从“能出图”到“看得准”的调优经验都写出来。适合正打算给自己的MySQL加监控、或者搭好了一半但对面板和告警不太满意的运维、DBA、后端开发参考。
1. 监控体系选型:为什么是Prometheus+Grafana而不是Zabbix
1.1 我试过的几种方案,以及最后留下的组合
在落到Prometheus之前,我其实不是没试过其他方案。
最早用的是Zabbix。Zabbix的问题在于组件重、配置复杂,虽然他可以监控很多网络设备和服务,但对MySQL这种需要细粒度采集状态的场景,模板写起来很繁琐。画图方面更是难受,Zabbix的图形能力偏弱,想看一条平滑的QPS曲线都得折腾半天。而且Zabbix的数据存在关系型数据库里,数据量一大,查询和备份都是负担。
后来也短暂用过一段时间的Shell脚本加定时任务:每5分钟用mysql -e "SHOW GLOBAL STATUS"把关键指标取出来,塞进数据库,再写个简单的PHP页面出图。这个方案在实例少的时候还能跑,一旦实例变多,脚本维护成本直线上升,而且历史数据基本只能看个趋势,没法交互式钻取,告警更是只能靠邮件脚本。说实话,这种土办法最大的价值是让我彻底明白了一个道理:监控系统不是“能取数就行”,而是要从采集、存储、可视化和告警四个维度一起考虑。
最后让我下决心换成Prometheus+Grafana的是几个硬性原因:组件轻、部署快,拉取模型(Pull)天然适合采集MySQL这类通过Exporter暴露指标的服务;时序存储对监控数据的压缩和查询效率远高于关系型数据库;Grafana的画图能力在开源领域几乎找不到对手,拖拽就能做出一块专业的大盘;再加上社区生态丰富,MySQL的Exporter和现成的Dashboard模板都有现成方案可以抄作业。
1.2 这套体系的运行链路:Exporter、Prometheus、Grafana各管哪一段
整个监控链路可以理解为三段各司其职:
text复制MySQL -> mysqld_exporter -> Prometheus -> Grafana
|
v
Alertmanager(告警通知)
mysqld_exporter是部署在MySQL机器上的采集器,本质上是一个小型的HTTP服务。它通过SQL查询MySQL内部的性能数据(比如全局状态变量、InnoDB指标、复制状态),把这些指标转换成Prometheus能识别的metrics格式,暴露在9104端口上。Prometheus按照配置的间隔主动去抓取这个端口,把数据存进自己的时序数据库,同时周期性地评估告警规则。Grafana则负责从Prometheus查询数据,渲染成图表。
这段链路里,Prometheus的Pull模型是很有优势的:Exporter不需要主动上报,Prometheus统一控制抓取频率和超时,监控端天然知道监控目标是否活着。相比Push模型(比如Agent主动上报到服务端),Pull模型在排查“监控自身挂了”和“被监控对象挂了”这两个问题上更清晰。
1.3 选型看的几个硬指标
如果你还在犹豫要不要用这套组合,我建议从这几个维度去判断:
| 维度 | 关注点 | 亲身体会 |
|---|---|---|
| 部署复杂度 | 组件数量、配置难度 | Prometheus本身是单个二进制文件,Grafana也是单包,没有额外依赖数据库 |
| 采集能力 | 是否有现成Exporter | mysqld_exporter成熟且活跃,各种MySQL指标覆盖很全 |
| 可视化能力 | 图表类型、面板定制 | Grafana支持变量、下拉框、联动,能做出接近商业产品的效果 |
| 告警能力 | 规则编写、通知渠道 | Prometheus统一用PromQL写规则,支持多级标签和for持续时间 |
| 运维成本 | 存储、升级、扩展 | 时序库单机模式就能顶住中小规模监控,放大规模可以用联邦或远程存储 |
从我实际用的体验来说,这套组合最大的好处是“下限低、上限高”。小规模环境一台机器就能全跑起来,大规模场景可以通过联邦、远程存储、Operator托管在K8s里横向扩展。这也是为什么它在近几年能迅速成为监控领域事实标准的原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 指标采集第一关:mysqld_exporter的部署与权限配置
2.1 Exporter到底在采集什么:从SHOW语句到性能库的读取
mysqld_exporter的采集原理其实不复杂。它的源代码里定义了一批采集器,比如global_status、global_variables、innodb、slave_status、performance_schema等,启动后周期性执行对应的查询语句,把结果转成Prometheus指标。
对于MySQL 5.6及以下的老版本,Exporter主要依赖SHOW GLOBAL STATUS、SHOW GLOBAL VARIABLES、SHOW MASTER STATUS这些命令来取数。对于MySQL 5.7及以上,它还会从performance_schema、sys库里读取更细粒度的统计数据。这也是为什么要给监控账号授SELECT和PROCESS权限——它需要读取性能和状态相关的表。
理解这一点很重要:我们最终在面板上看到的每个指标,背后都对应一条SQL。比如mysql_global_status_threads_connected本质上就是执行SHOW GLOBAL STATUS LIKE 'Threads_connected'取到的值,mysql_global_variables_max_connections则是问SHOW GLOBAL VARIABLES LIKE 'max_connections'。所以在排查“指标为什么没出来”时,第一步先手动执行对应的SQL,看能否拿到数据,是最快的定位方式。
2.2 最小权限账号的创建与配置
监控账号的原则是“最小权限”,能读能看就行,千万不要用root。下面是创建账号的SQL,我用的是MySQL 8.0语法,MySQL 5.7也同样适用:
sql复制CREATE USER 'mysqld_exporter'@'127.0.0.1' IDENTIFIED BY '强密码' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'mysqld_exporter'@'127.0.0.1';
FLUSH PRIVILEGES;
几个细节说明:
WITH MAX_USER_CONNECTIONS 3是限制这个账号最多只能同时建立3个连接,防止Exporter异常时把MySQL连接池吃满。这个是我踩过坑之后才加上的——之前有一次Exporter短时间连续高频抓取,导致监控账号的连接数暴涨。- 权限里的
REPLICATION CLIENT不是必须的,但它能读取主从复制状态,比如Seconds_Behind_Master、复制的IO/SQL线程运行状态。如果你的实例是主从架构,建议加上。 - 这里把允许访问的主机设成
127.0.0.1,是为了限制只允许本机的Exporter来连。如果Exporter和MySQL不在同一台机器,需要改成Exporter所在机器的IP。
在Exporter这边,连接信息通过环境变量DATA_SOURCE_NAME传入,格式和连接串一致:
bash复制export DATA_SOURCE_NAME='mysqld_exporter:密码@tcp(127.0.0.1:3306)/'
注意这个连接信息在部分版本里不允许带密码特殊字符,比如@、#,如果密码里有特殊字符会解析失败,这种情况需要对字符串做URL编码,或者用my.cnf配置文件等形式来传。
2.3 二进制部署与Docker部署的差异
mysqld_exporter的部署方式,我两种都试过,分别说下。
二进制方式适合没有容器环境的传统机器:
bash复制wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.1/mysqld_exporter-0.15.1.linux-amd64.tar.gz
tar xf mysqld_exporter-0.15.1.linux-amd64.tar.gz
cd mysqld_exporter-0.15.1.linux-amd64
export DATA_SOURCE_NAME='mysqld_exporter:密码@tcp(127.0.0.1:3306)/'
./mysqld_exporter --config.my-cnf=~/.my.cnf --collect.info_schema.processlist --collect.info_schema.tablestats --collect.info_schema.tables --collect.info_schema.innodb_tablespaces --collect.info_schema.innodb_metrics --collect.global_status --collect.global_variables --collect.slave_status --collect.performance_schema.*
启动后可以先用curl http://127.0.0.1:9104/metrics验证一下,能看到一连串mysql_开头的指标就说明Exporter工作正常。
这里建议显式指定需要开启的采集器。默认开启的采集器虽然覆盖了大部分核心指标,但像info_schema.processlist、info_schema.tablestats这些默认不开启,而这些对排查连接状态和表查询压力很有帮助。如果想省事,直接在启动时加--collect.all,需要留意采集项越多,数据库开销也越大。
Docker方式适合容器化环境,配置文件用--config.my-cnf传进去更干净:
bash复制docker run -d \
--name mysqld-exporter \
-p 9104:9104 \
-e DATA_SOURCE_NAME="mysqld_exporter:密码@tcp(127.0.0.1:3306)/" \
prom/mysqld-exporter:v0.15.1 \
--collect.info_schema.processlist \
--collect.info_schema.tablestats \
--collect.info_schema.innodb_metrics
无论是二进制还是Docker,部署完成后建议用systemd或者Docker的restart: unless-stopped策略管理进程,确保机器重启后Exporter自动恢复。如果Exporter挂了,Prometheus这边会直接标记目标为DOWN,面板上会出现明显的断线状态,这也是一个间接但很有效的“MySQL实例存活”监控信号。
3. 决定监控质量的指标清单:连接数、慢查询、Buffer Pool与主从状态
3.1 必看的核心指标分类与含义
指标采集出来了,接下来最关键的是要知道该看哪些指标。这里列一份我实际监控MySQL以来最常用、最有价值的核心指标清单,按监控目标分组:
| 监控目标 | 指标名称 | 含义与说明 |
|---|---|---|
| 连接数 | mysql_global_status_threads_connected |
当前已建立的连接数,核心关注点 |
| 连接上限 | mysql_global_variables_max_connections |
max_connections配置值,用于计算连接数使用率 |
| 慢查询 | mysql_global_status_slow_queries |
累计慢查询次数,需要配合rate/increase看增量 |
| 查询量 | mysql_global_status_queries |
累计执行语句总数,QPS的计算基础 |
| 吞吐量 | mysql_global_status_bytes_received / mysql_global_status_bytes_sent |
入/出网络流量,判断业务压力 |
| Buffer Pool命中率 | mysql_global_status_innodb_buffer_pool_read_requests / mysql_global_status_innodb_buffer_pool_reads |
逻辑读和物理读的累计值,计算命中率 |
| InnoDB缓冲池大小 | mysql_global_variables_innodb_buffer_pool_size |
InnoDB缓冲池配置大小 |
| 主从延迟 | mysql_slave_status_seconds_behind_master |
主从延迟秒数,没有复制时为0 |
| 复制线程状态 | mysql_slave_status_slave_io_running / mysql_slave_status_slave_sql_running |
IO/SQL线程是否在运行,1运行、0停止 |
| 临时文件/临时表 | mysql_global_status_created_tmp_tables / mysql_global_status_created_tmp_disk_tables |
临时表数量,磁盘临时表过多提示排序或连接性能问题 |
这些指标里,连接数和使用率是我最常看的:连接数除以max_connections算出来的使用率超过70%就要开始警惕,超过80%基本意味着随时可能出现“Too many connections”。
3.2 数据保留与抓取频率的设计
这块经常被忽略,但直接关系到监控系统的稳定性和磁盘成本。
Prometheus默认的数据抓取间隔是15秒,也就是每15秒对每个采集目标拉取一次。按默认配置计算,一个mysqld_exporter大约会暴露400到600个指标序列(取决于开启的采集器),一台实例一天产生的数据点大概是:
500 序列 × (86400秒 / 15秒) ≈ 288万个数据点
虽然Prometheus底层有压缩,单个数据点经过编码压缩后通常只占不到2字节,但一天下来一个实例的监控数据也要几十MB。如果监控的实例多了,或者把scrape_interval调成5秒、1秒,存储增长会非常明显。
我在前期的配置习惯是:
scrape_interval默认保持15秒,不要低于10秒。采集太频繁对Exporter和MySQL都是负担,实话说MySQL的缓存状态变量查询开销并不小。- 数据保留期用默认的15天就够了,配合告警规则,能覆盖绝大多数故障复盘的需要。
- 如果确实需要长期留存历史趋势,优先考虑给Prometheus配置远程存储,而不是无限调大
retention参数。
3.3 主从延迟的监控要注意什么
主从延迟是MySQL监控里最容易被误读的指标之一。
mysql_slave_status_seconds_behind_master这个值在复制正常时是0或者很小的数字,但它有一个坑:当SQL线程在运行、但IO线程已经断开时,这个值可能仍然是0,你看到延迟为0就以为复制是好的,实际上主库的binlog已经传不过来了。所以,仅仅盯延迟秒数是不够的,必须同时监控slave_io_running和slave_sql_running这两个线程状态。
在Prometheus里,mysql_slave_status_slave_io_running的值通常是1或0,直接用这两个值做告警才是完整的复制监控:
yaml复制- alert: MysqlReplicaIoThreadDown
expr: mysql_slave_status_slave_io_running == 0
for: 1m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 复制IO线程已停止"
另外一点,如果一台MySQL上有多个复制通道(多源复制),mysql_slave_status_*指标上会有channel_name标签来区分,面板上记得把channel_name放到变量里,方便按通道筛选。如果是单主单从或者传统主从模式,这个标签可能不存在,直接按instance标签看就行。
4. Prometheus抓取配置与告警规则:从磁盘告警到连接数告警的落地写法
4.1 抓取配置的核心字段与常见误区
Prometheus的抓取配置在prometheus.yml里。这个文件是我最开始觉得最“玄学”的地方,后来发现核心概念其实很清晰。
一段基础的MySQL抓取配置长这样:
yaml复制global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'mysql'
static_configs:
- targets:
- '192.168.1.10:9104'
- '192.168.1.11:9104'
labels:
env: 'production'
有个细节值得注意:targets里填的是Exporter的地址,端口是9104,不是MySQL的3306。很多人第一次配置的时候会填错成3306,结果一定抓不到数据。Exporter只是替MySQL把指标翻译出来,Prometheus抓的是Exporter。
labels标签不是必须的,但它特别好用。比如我在targets下面额外加了env或者role标签,后面写告警规则、看面板时就可以按这个标签分组过滤,比如只看主库、只看某个环境。
另一个常见误区是metrics_path。默认值就是/metrics,如果Exporter没改路径,这段不用配置。但如果你自己写了反向代理,把Exporter端口代理到了80,那就需要显式加metrics_path: /metrics来保证请求路径正确。
4.2 告警规则文件的编写逻辑
告警规则写在单独的规则文件里,然后在prometheus.yml里通过rule_files加载:
yaml复制rule_files:
- "rules/*.yml"
规则文件的格式是分组管理。我最常用的三组告警规则如下:
连接数使用率告警:
yaml复制groups:
- name: mysql_alerts
rules:
- alert: MysqlConnectionUsageHigh
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "实例 {{ $labels.instance }} 连接数使用率超过80%"
description: "当前连接数 {{ $value | humanizePercentage }},请检查应用连接池是否异常"
这条规则的逻辑是拿当前连接数除以max_connections,得到一个0到1之间的比例,超过0.8就触发。这里的{{ $value | humanizePercentage }}是Prometheus模板里的格式化函数,会把0.85渲染成85%,告警信息看起来更直观。
慢查询数量告警:
yaml复制 - alert: MysqlSlowQueryHigh
expr: increase(mysql_global_status_slow_queries[5m]) > 30
for: 2m
labels:
severity: warning
annotations:
summary: "实例 {{ $labels.instance }} 最近5分钟慢查询数超过30"
这里要注意mysql_global_status_slow_queries是一个累加计数器,值只会一直涨,所以不能直接用原始值判断,必须用increase()函数计算一段时间内的增量。[5m]表示取最近5分钟的数据窗口。 这条规则的阈值写的是30,具体数字要看业务情况,我测下来某些报表库5分钟几百个慢查询是常态,直接按固定值判断会一直告警。
主从延迟告警:
yaml复制 - alert: MysqlReplicaLagHigh
expr: mysql_slave_status_seconds_behind_master > 30
for: 3m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 主从延迟超过30秒"
description: "当前延迟 {{ $value }}秒,请检查复制链路或磁盘IO"
主从延迟的告警为什么建议加for: 3m?因为在主从切换、或重启复制时,延迟出现几秒到几十秒的瞬时抖动非常正常。直接for: 1m就会频繁误报,加一个短持续条件能过滤掉一大半噪音。
磁盘告警这里多说一句。mysqld_exporter本身不暴露磁盘空间指标,磁盘监控通常由node_exporter承担,这类系统层面指标的采集方式不同,但告警思路一样。就拿node_exporter的磁盘空间告警做一个示例:
yaml复制 - alert: NodeDiskUsageHigh
expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 0.9
for: 10m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 磁盘使用率超过90%"
这里用node_filesystem_avail_bytes除以node_filesystem_size_bytes得到可用率,再用1减得到使用率。fstype过滤掉tmpfs和overlay这类虚拟文件系统,避免把内存文件系统也算进去干扰判断。
4.3 聚合查询的常用模式:SUM BY、rate、max_over_time
这一节对应很多人会搜的“Grafana sum by”这类问题,其实这些都是在PromQL查询里最常见的操作。
sum by的作用是按某个标签维度做汇总。比如你监控了5台MySQL实例,查询每个实例的连接数都用原始指标名,那么在图上就有5条线。如果你想看所有实例的总连接数,可以写:
promql复制sum(mysql_global_status_threads_connected)
如果想按instance分组看每个实例自己的连接数,用:
promql复制sum by (instance) (mysql_global_status_threads_connected)
它和直接查原始指标的区别在于,如果同一实例上有多个mysqld_exporter进程,或者数据里存在额外的标签组合,sum by会帮你合并成一个值,避免曲线重复或多线叠加。
rate则是专门用来处理计数器类型指标的函数。比如查询QPS(每秒查询数):
promql复制sum by (instance) (rate(mysql_global_status_queries[5m]))
rate计算的是窗口期内每秒平均增量,更适合看趋势和速率。如果看的是短时间窗口内的突发变化,可以用irate,它的粒度更细、对瞬时波动的响应更快,但在长时间窗口(比如[1h])下曲线会锯齿状很明显。日常看板和告警我基本都以rate为主。
max_over_time用于看某个指标在一段时间内的最大值,比如看主从延迟的峰值:
promql复制max_over_time(mysql_slave_status_seconds_behind_master[10m])
在面板上配合min_over_time、avg_over_time一起,可以实现“实时值+峰值+平均值”的多层展示,一眼就能判断指标是平稳还是大幅抖动。
5. Grafana面板搭建:从导入模板到按需定制
5.1 数据源配置的注意点
Grafana本身不存监控数据,它只是把Prometheus的数据“画”出来,所以第一步是添加数据源。在Grafana左侧菜单进入Configuration -> Data Sources -> Add data source,选择Prometheus,填上Prometheus的HTTP地址,比如http://localhost:9090。
这里有一个很容易出问题的点:如果Prometheus和Grafana不在同一台机器上,地址不能填localhost,要用Prometheus所在机器的实际IP。生产环境里,如果两者之间有防火墙或安全组,记住了要放行9090端口。还有一点,Grafana服务端和浏览器访问的数据源地址是两回事——Grafana的服务器会向后端地址发起请求,所以URL里面的地址必须从Grafana服务器的角度能访问到,而不是从你浏览器的角度。
保存之后点Save & Test,如果显示Successfully queried the Prometheus API,说明Grafana已经能正常连上Prometheus。
5.2 模板导入的便利与限制
Grafana社区有很多现成的MySQL监控Dashboard模板,在Grafana官网Dashboards页面搜索“MySQL”就能找到。比较常用的是ID为7362的经典模板,它对mysqld_exporter默认暴露的指标覆盖得挺全,包含连接数、QPS、慢查询、InnoDB状态等,导入之后基本开箱即用。
导入的方式很简单:Dashboards -> New -> Import,输入模板ID,选择数据源,点导入就行。
但这里我想提醒一下:模板导入之后不要直接撒手不管。这类社区模板是基于某个特定版本的mysqld_exporter写的,如果你的Exporter版本较新或者较旧,指标名可能存在差异,导致某些Panel显示No data。另外,模板里的大盘普遍很“重”,一个模板十几个Panel全堆在一个页面上,加载会变慢,而且很多图表你并不需要。
所以我现在的习惯是:模板只作为参考,导入后按自己的核心指标清单做减法,把不需要的Panel删掉,把不够的补上,最终形成自己团队的“标准大盘”。实话说,团队里如果每个人都开着十几张图,反而不容易看到关键信息。
5.3 自定义一屏核心面板:变量、查询语句和图表类型的搭配
自己搭面板时,建议先想清楚“最关心的指标在哪一屏”。我最终的MySQL主控大盘长这样:连接数使用率、QPS、慢查询速率、主从延迟、InnoDB缓冲池命中率、磁盘空间使用率。这几个指标放在一屏上,故障定位的时候基本不用翻页。
配置面板的核心是三个要素:变量、查询、图表类型。
变量是可以让面板交互起来的关键。在Dashboard设置里定义变量,比如一个instance变量,类型选Query,查询语句填:
promql复制label_values(mysql_global_status_threads_connected, instance)
这样面板顶部会出现一个实例下拉框,选择不同实例时,所有面板的数据都会跟着联动过滤。不选的话,默认展示全部实例。
查询语句按指标类型来选择:
- 连接数面板,用
sum by (instance) (mysql_global_status_threads_connected),图表类型选时间序列,能看多实例趋势。 - QPS面板,用
sum by (instance) (rate(mysql_global_status_queries[5m])),图表类型选时间序列。 - 慢查询面板,用
sum by (instance) (increase(mysql_global_status_slow_queries[5m])),时间序列即可。 - 主从延迟面板,用
mysql_slave_status_seconds_behind_master,如果有多实例,也套一层sum by (instance)。 - Buffer Pool命中率,需要一个计算表达式:
promql复制(1 - (mysql_global_status_innodb_buffer_pool_reads / mysql_global_status_innodb_buffer_pool_read_requests)) * 100
这个指标在Grafana里建议选择Stat(统计图)类型,显示一个百分比数字,配合单位设置percent,看起来最直观。
- 磁盘空间使用率,查询node_exporter的指标,上面已经列过表达式,图表类型推荐用
Gauge或者Bar gauge,超过阈值自动变色。
图表的颜色阈值可以在面板的Thresholds里设置,比如磁盘使用率:绿色小于80%、黄色80%到90%、红色超过90%,一目了然。
6. 上线后的真实踩坑与调优:从“能看到”到“看得准”
6.1 认证协议类型导致客户端报错的问题
这是第一次用mysqld_exporter监控MySQL 8.0时最常踩的坑。MySQL 8.0默认的认证插件改成了caching_sha2_password,而一些版本较老的客户端、驱动、Exporter连接器并不支持这个协议,连接时会直接报错,提示类似:
Authentication plugin 'caching_sha2_password' cannot be loaded
在.NET开发里还常见这个错误:
Firedac phys mysql client does not support authentication protocol requested
本质是同一件事:客户端不支持服务端的默认认证插件。解决办法有两个:
一是在创建监控账号时,显式指定老的兼容认证插件:
sql复制CREATE USER 'mysqld_exporter'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY '强密码';
二是在MySQL 8.0的配置文件里改掉默认认证插件。不过你要知道这个改动影响面较大,会影响所有新建账号,所以只建议在兼容性需求强烈时用。我的建议是优先方案一,只给监控账号开一个小口,安全且影响面小。
从MySQL 8.4开始,mysql_native_password插件本身也已经被标为废弃,未来可能被移除。如果直接上MySQL 8.4或者更新版本,监控账号就不能依赖这个方法了,这时最好升级Exporter和相关客户端到支持caching_sha2_password的版本,而不是继续用老协议。
6.2 Exporter轮询频率太高,反而把MySQL拖慢
这个坑我印象太深了。有一阵子为了“实时监控”,我把scrape_interval从15秒改成了5秒,后来甚至改过1秒,想着能第一时间看到数据。结果运行几小时后,MySQL慢查询日志里出现大量的SHOW GLOBAL STATUS、SHOW GLOBAL VARIABLES查询,虽然单次开销不大,但架不住频率高、连接多,直接增大了数据库的负载,整个实例的QPS都被带高了。
这个事给我一个教训:监控系统本身就是被监控系统的外部负载,频率越高付出的代价越大。对MySQL这类状态型监控来说,15秒的抓取间隔已经是相当“实时”了,连数据库自己很多指标都是秒级刷新。如果业务上真的需要秒级甚至毫秒级的数据,那应该考虑专门的性能监控方案,而不是硬调Prometheus抓取频率。
6.3 状态变量的“累计值”陷阱
第一次在面板上看到mysql_global_status_queries的时候,我一度以为监控出错了——曲线从0一路涨到几千万,完全看不出任何规律。后来才反应过来,这是一个累计计数器,从MySQL启动到现在的总查询次数,它天然会一直增长。
对于这类Counter类型的指标,正确用法是配合rate()或increase()来看增量趋势,而不是直接查原始值。比如QPS、慢查询速率、网络吞吐,全部要套上rate或者increase才能反映真实的动态变化。
这里还有一个容易踩的点:如果用increase(mysql_global_status_slow_queries[5m])看5分钟增量,窗口内如果包含MySQL重启的时刻,计数会被重置,增量值可能会变得很大甚至出现负值,这是正常现象。在告警规则里如果遇到“重启后误报”,可以通过for持续条件来过滤,或者在规则里写absent_over_time避免问题。
6.4 告警风暴的收敛:持续时间、表达式与静默
监控上线初期,最让人头疼的不是没告警,而是告警刷屏。我经历过一次大版本升级后,同时收到连接数、慢查询、主从延迟、磁盘空间好几类告警,每个人手机都在响,但实际只是主从切换过程中的正常波动。
告警规则真正要做的不是“有异常就报”,而是“有值得处理的异常才报”。后面我做了几个调整:
- 给规则加上
for持续时间,异常持续几分钟才触发,瞬时抖动不会误导。 - 表达式里尽量用“比例”而不是“绝对值”。比如连接数用
使用率>80%,而不是连接数>1000——因为不同实例的max_connections配置不同,绝对值没有通用性。 - 对维护窗口内的操作,在Alertmanager配置静默规则,或者直接在Grafana里做时间范围过滤。
- 告警分级别。
warning只是提醒,critical才推给值班人,避免紧急消息被无关紧要的告警淹没。
6.5 数据保留与磁盘成本
Prometheus默认保留15天数据,这个窗口对日常监控和故障复盘通常够用。但如果你的机器磁盘比较小,尤其和Grafana共用一台机器时,要及时关注Prometheus数据目录的大小。我曾经跑过一台256GB磁盘的机器,Prometheus数据目录占掉100多GB,差点撑爆。
控制磁盘占用可以从几个角度入手:
- 减少不必要的Exporter采集项,比如关掉几乎用不到的采集器。
- 调整
scrape_interval,从15秒改成30秒,数据量直接减半,但监控实时性会下降一些。 - 对长期趋势数据做降采样,比如用
recording rule把1小时聚合的结果存成新指标,再用这个新指标做长期的趋势图。
另一个非常实用的功能是recording rule(录制规则)。它可以把复杂、耗时的PromQL查询在Prometheus内部定时计算好,把结果存成新指标,查询面板时直接查这个新指标,响应速度快很多。比如把QPS查询提前算好:
yaml复制rules:
- record: job:mysql_queries_per_second:rate5m
expr: sum by (instance) (rate(mysql_global_status_queries[5m]))
以后面板里直接查job:mysql_queries_per_second:rate5m就行,不用每次查询都现算。
7. 最后再分享一点个人体会
这套监控从部署到跑到今天,我最大的感受是:监控的价值不在于图有多好看,而在于能不能在业务感知之前发现问题,以及故障发生时能不能快速定位。
指标不要追求多,把几个核心指标看准了比堆一百个图表有用。盯连接数、慢查询、主从状态、磁盘空间这四个维度,已经能覆盖线上MySQL绝大多数“卡了、慢了、挂了”的场景。告警规则也不是越多越好,我最终稳定的配置也就七八条规则,但每一条都是经过实际故障检验过的,比一开始照抄模板的二十几条规则可靠得多。
如果后续还想往下深挖,方向无非是这几块:一是把告警通知接入企业微信、钉钉或Slack,让告警真正到达人的手机;二是引入Alertmanager的路由和静默机制,做更精细的告警治理;三是如果MySQL跑在K8s里,可以用Prometheus Operator来托管监控组件,让扩缩容节点自动纳入监控范围;四是加一台node_exporter补上系统层指标(CPU、内存、磁盘、网络),组成完成的“系统+数据库”监控视图。数据库监控只是第一步,最后你会发现自己想要的是一整套能快速定位问题链路的基础设施。
从最开始半夜爬起来救火,到现在打开Grafana就能说清系统和数据库的状态,这套链路帮我省下的时间远不止搭建时花掉的那些。建议你也动起手来搭一套,从一个小实例开始,跑通了再慢慢扩大范围,过程中的收获会比你看十篇教程都多。
