1. 整体方案设计与监控架构解析
监控MySQL这件事,困扰过很多运维和开发。早期常用方式无非几种:写Shell脚本定时跑SELECT,把结果存到文本或数据库;稍微正规一点的会部署一套Zabbix;还有的干脆直接用云厂商自带的监控面板。这些方案要么看不到历史趋势,要么告警能力弱,要么扩缩容麻烦,总有不顺手的地方。后来我切到Prometheus + mysqld_exporter + Grafana这套组合,算是把“看MySQL状态”这件事彻底理顺了。
先解释一下这套架构为什么合理。Prometheus是个时间序列数据库,它定期去拉取监控数据,拉到以后按指标名和时间戳存下来。MySQL这边自己不直接暴露监控接口,需要一个中间层把MySQL内部的状态翻译成Prometheus认识的格式,这个中间层就是mysqld_exporter。exporter连接MySQL执行SHOW GLOBAL STATUS、SHOW VARIABLES、SHOW PROCESSLIST这些查询,把返回结果转换成标准的Prometheus指标,比如mysql_global_status_threads_connected。最后Grafana负责把Prometheus里的数据画成图表,提供可视化和告警入口。
整套方案里,Prometheus只负责采集、存储和查询,不做展示;Grafana只负责展示和告警,不碰数据;exporter是唯一接触MySQL的组件,权限控制都集中在它身上。三层解耦,职责清晰,出了问题也好排查。对比我之前用过的Zabbix,Prometheus这套在自定义监控项和告警规则上灵活得多——Zabbix加一个监控项要写key,要配置item原型,麻烦得很,Prometheus这边只要写一行PromQL就能表达清楚。
选型背后的逻辑其实很简单:为什么不用传统脚本采集?因为脚本每次执行都是瞬时状态,存不下来,也就谈不上趋势分析和基线画像。为什么不用商业监控软件?成本高、粘性强、DataSource外部导出麻烦。Prometheus是CNCF项目,生态大,周边工具多,社区模板丰富,部署和维护成本都不高。这套组合最适合的群体是:中小规模MySQL集群的运维人员、后端开发自己兼任运维的,以及想彻底搞清楚MySQL运行状态的技术爱好者。
接下来的部署,我会以Linux环境为例,一步一步说清楚从安装到出图到告警的完整链条。版本说明一下:Prometheus 2.45+,mysqld_exporter 0.15+,Grafana 10.x,MySQL 5.7或8.0均可。这套配置我实测过,直接抄作业没问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与核心组件部署
2.1 mysqld_exporter的三种部署方式详解
mysqld_exporter是连接Prometheus和MySQL的关键桥梁,部署方式直接影响安全性和可维护性。我在实际项目里用过三种方式,按推荐程度从高到低排列:
- 容器方式(Docker):适合Docker环境比较成熟的团队,一条命令能跑起来,升级方便,但要注意映射端口和数据卷。
- 二进制方式:下载tar包解压直接跑,适合裸机环境或对容器有顾虑的场景,全裸二进制,依赖最少。
- systemd托管方式:本质还是二进制,但用systemd管理进程生命周期,开机自启、异常拉起,这是我生产环境最推荐的方式。
先说二进制安装的具体步骤。前往Prometheus官网下载mysqld_exporter的Linux amd64版本,解压后把二进制文件复制到/usr/local/bin目录:
bash复制cd /tmp
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.1/mysqld_exporter-0.15.1.linux-amd64.tar.gz
tar xvf mysqld_exporter-0.15.1.linux-amd64.tar.gz
sudo cp mysqld_exporter-0.15.1.linux-amd64/mysqld_exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/mysqld_exporter
接着需要准备一个专门的监控账号。这个账号的权限必须按最小化原则来:Promise的exporter需要PROCESS权限来查看线程状态,需要REPLICATION CLIENT权限来读取主从复制信息,还需要能查询performance_schema库。注意,这一步绝对不能偷懒用root账号,否则一旦exporter被入侵,等于把整个MySQL拱手送人。
在MySQL里执行:
sql复制CREATE USER 'mysqld_exporter'@'localhost' IDENTIFIED BY '强密码';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'mysqld_exporter'@'localhost';
FLUSH PRIVILEGES;
创建配置文件保存MySQL账号密码。exporter支持通过环境变量或配置文件两种方式传递数据库连接信息,配置文件方式更安全,不会在进程列表里泄漏密码。我在/etc/prometheus/下创建.my.cnf:
ini复制[client]
user=mysqld_exporter
password=强密码
然后在systemd服务文件里指定配置路径和监听端口:
ini复制[Unit]
Description=Prometheus MySQL Exporter
After=network.target
[Service]
Type=simple
User=prometheus
Group=prometheus
ExecStart=/usr/local/bin/mysqld_exporter --config.my-cnf=/etc/prometheus/.my.cnf --collect.info_schema.processlist --collect.global_status --collect.global_variables --collect.slave_status --web.listen-address=0.0.0.0:9104
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
启动后验证一下接口是否正常出数据:
bash复制curl http://localhost:9104/metrics | grep mysql_up
mysql_up 1
mysql_up的值是1说明exporter已经成功连上MySQL开始采集。0则表示失败,后面的排错章节我会专门说这个。
2.2 Prometheus主服务安装与采集目标配置
Prometheus主服务安装相对直接。官方提供编译好的二进制包,也支持Docker一键启动。我习惯用二进制部署到裸机上,因为资源占用可控、排查方便。下载Prometheus最新版解压后,把可执行文件复制到/usr/local/bin,创建数据目录,然后写配置:
yaml复制global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'mysql'
static_configs:
- targets: ['192.168.1.10:9104']
labels:
instance: 'mysql-01'
注意scrape_interval的取值。15秒的采集频率对MySQL监控来说已经足够,可以看到分钟级的趋势变化。如果设成1秒,数据点太密,Prometheus存储膨胀,反而失去了重点——我们监控的是趋势和异常,不是抖动。
启动Prometheus后,在浏览器打开http://localhost:9090/targets,能看到mysql这个job,状态为UP就说明Prometheus已经能正常拉到exporter的数据。这一环节最常见的问题是防火墙挡端口9104,或者exporter监听地址写成了127.0.0.1导致外部Prometheus无法访问。
2.3 Grafana部署与数据源配置
Grafana的安装也走二进制路径,解压后配置默认端口3000,首次登陆用admin/admin,进入后第一步先强制改密码,第二步添加DataSource。这里有一个经常踩的坑:DataSource里的URL字段要写Prometheus的HTTP地址,不是暴露给浏览器的地址。如果Grafana和Prometheus在同一台机器上,就填http://localhost:9090,如果分离部署,需要确认Prometheus的--web.listen-address监听在0.0.0.0上而非仅本机回环。
数据源配置完成后,测试连接提示Success,然后就可以导入现成的Dashboard模板了。Grafana社区有大量MySQL监控模板,使用频率最高的是模板ID 7362(Percona MySQL Overview)和6239(MySQL Overview)。这两个模板覆盖了连接数、缓冲池、慢查询、主从延迟等核心指标,图表组织合理,省去自己逐个画图的麻烦。
导入模板后建议先跑一天数据再看看图表是否正常。如果某些panel显示No data,大概率是exporter没开对应的collecter,比如模板里有replication相关的图,你却没用--collect.slave_status这个参数。这种情况按需调整exporter的collecter参数然后重启即可。
3. MySQL监控指标采集与关键指标解读
3.1 exporter采集原理与指标映射机制
理解了指标从哪来,才能用好这些指标。mysqld_exporter并非凭空产生数据,它本质上是把MySQL的现有状态接口翻译成Prometheus的格式。它采集的数据源主要有四个:SHOW GLOBAL STATUS、SHOW GLOBAL VARIABLES、SHOW PROCESSLIST、SHOW SLAVE STATUS(8.0是SHOW REPLICA STATUS)。每个状态值都会被转换成一个带说明的Prometheus指标,比如:
- SHOW GLOBAL STATUS里的Threads_connected变成mysql_global_status_threads_connected
- SHOW GLOBAL VARIABLES里的max_connections变成mysql_global_variables_max_connections
- SHOW PROCESSLIST里的进程数量通过collect.info_schema.processlist参数采集,转成按state分组的mysql_info_schema_processlist_count
这里要特别说一个细节:SHOW GLOBAL STATUS返回的值大部分是累计计数(Counter),比如Com_select是自从MySQL启动以来执行了多少次SELECT语句;而另一些是瞬时状态(Gauge),如Threads_connected。exporter原样保留这些区别。你在写PromQL的时候必须区分清楚:Gauge类型的指标直接查当前值,Counter类型的指标要配合rate()函数看变化速率,否则你拿Com_select当前值去看“每秒查询量”,得出来的数字完全是错的。
举个例子,QPS这个指标的计算方式是:
promql复制rate(mysql_global_status_queries[1m])
意思是一分钟内每秒查询数的平均值。如果不做rate,直接查mysql_global_status_queries,你会得到一个非常大的累计值,并且随时间单调递增,对判断当前负载毫无帮助。这个原理在Grafana的很多官方面板里已经帮你处理过了,但如果你自己写面板或写告警规则,必须掌握。
3.2 6类核心监控指标逐一拆解
整理一下我最关注的六类指标,每类对应不同的故障场景:
连接数与连接池。Threads_connected是最直观的负载指标,它表示当前打开的连接数。配合max_connections可以算连接使用率:mysql_global_status_threads_connected / mysql_global_variables_max_connections。使用率超过80%要引起警惕,超过95%基本意味着连接即将耗尽,新请求无法建立连接,业务会直接报too many connections错误。另外还有个指标mysql_global_status_max_used_connections,表示历史最高连接数,我习惯用这个指标判断是否需要扩容max_connections。
QPS与TPS。如前所述,QPS用rate(mysql_global_status_queries[1m])计算。TPS通常用Com_commit和Com_rollback来估算,一条事务要么提交要么回滚:rate(mysql_global_status_commands_total{command="commit"} + mysql_global_status_commands_total{command="rollback"}[1m])。QPS突然升高,常见原因是业务流量上涨或者有人在跑大查询,配合慢查询指标可以交叉验证。
慢查询。mysql_global_status_slow_queries是累计值,同样需要rate()。慢查询数量突增,说明SQL性能在恶化。但要区分的是:慢查询变多的情况可能是某个复杂SQL需要全表扫描,也可能是系统整体负载过高导致普通SQL也变慢。这两种问题的处置方向不同,前面要加大SQL优化力度,后面要扩容或限流。最好配合锁等待指标一起看。
InnoDB缓冲池命中率。InnoDB Buffer Pool是内存中的缓存区域,命中率高说明大部分读操作都落在内存里,不必访问磁盘。命中率低会引起大量磁盘IO,进而拉高整体延迟。计算方法是:(mysql_global_status_innodb_buffer_pool_read_requests - mysql_global_status_innodb_buffer_pool_reads) / mysql_global_status_innodb_buffer_pool_read_requests。好的情况下这个值应该在99%以上,如果低于95%,说明缓冲池容量配置太小,需要调大innodb_buffer_pool_size。
主从复制状态。这是MySQL高可用架构里最容易出问题的环节。exporter通过--collect.slave_status采集复制信息,关键指标是mysql_slave_status_slave_sql_running和mysql_slave_status_slave_io_running,正常值都是1。任何一个变成0,都表示复制中断,数据不一致的风险开始积累。另外Seconds_Behind_Master指标反映从库落后主库的秒数,长时间大于0且持续增长,说明从库追不上主库,可能是在执行大事务或者从库硬件性能不足。
临时表与磁盘排序。mysql_global_status_created_tmp_disk_tables表示在磁盘上创建的临时表数量,配合rate()看速率。磁盘临时表过多通常是因为GROUP BY或ORDER BY的行太大,内存临时表装不下,这时要关注tmp_table_size和max_heap_table_size的配置,必要时优化SQL减少中间结果集。
这些指标不是孤立的,它们共同描述了一个MySQL实例的健康画像。我曾经遇到过一台磁盘写满的数据库,表面上看是磁盘利用率指标告警,实际深挖是慢查询增多、InnoDB刷新脏页频繁、临时表落盘增多三个因素叠加导致的IO风暴。如果只盯着单一指标,很容易被表象迷惑,所以建立指标关联分析的意识很重要。
4. 可视化面板构建与告警规则配置
4.1 Grafana面板导入与自定义优化
导入现成模板可以快速看到效果,但真正符合业务场景的监控面板一定要自己调整。我一般以Percona模板为基础,再根据实际架构增删panel。
以模板ID 7362为例,导入后在设置里把Prometheus数据源绑定好,刷新看效果。这个模板默认包含MySQL Overview、MySQL Summary、MySQL Replication等几个文件夹。如果你的环境是单实例没有主从,Replication文件夹里的图会显示No data,这是正常的——可以直接把对应的Row隐藏掉,或者干脆删掉,保持界面干净。
我自己习惯增加几个默认模板没有的panel。第一个是连接数使用率百分比图,用单个Gauge展示,颜色阈值设置在80%黄色、95%红色,这样从大屏上一眼就能看出连接池是否即将耗尽。第二个是Top SQL耗时和扫描行数,这个需要额外启用performance_schema的events_statements_summary_by_digest表采集功能(mysqld_exporter中已经隐含了一部分,但完整信息建议用percona的pmp工具或单独采集)。第三个是磁盘容量和inode使用率,这个其实不来自exporter,要配合node_exporter一块监控。
自定义面板时有几个操作细节值得注意:PromQL的查询语句要写在Query编辑器里,注意选择正确的Legend和Format — 一般用Time series。Rate区间我习惯用5m,比1m更平滑,能过滤掉瞬时毛刺;但告警规则里可能会改用1m以便更快捕捉异常,需要根据场景区分。面板的刷新时间间隔跟Prometheus的scrape_interval要协调好,如果Prometheus每15秒拉一次数据,Grafana面板设置30秒刷新比较合理,不必把刷新设太短。
设置面板的时候给每个图表写清楚说明和单位。比如连接数用short单位,慢查询速率用ops单位,延迟用seconds单位。这看起来是小事,但前后端团队协作时,监控大屏的“可读性”会直接影响告警处理效率。
4.2 Prometheus告警规则编写与Alertmanager联动
可视化只是让人看得见,真正让监控发挥威力的是告警。Prometheus的告警体系分两层:规则层和通知层。规则层定义“什么情况算异常”,每个规则最终会生成一条Alert;通知层由Alertmanager负责把Alert推送到钉钉、邮件或企业微信。
告警规则文件alert.yml先写好,再在prometheus.yml里用rule_files字段引用:
yaml复制groups:
- name: mysql_alert.rules
rules:
- alert: MySQLInstanceDown
expr: mysql_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "MySQL实例 {{ $labels.instance }} 已停止响应"
description: "MySQL实例无法连接,已持续1分钟以上,请立即处理"
- alert: MySQLHighThreadsConnected
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL连接数超过最大值的80%"
description: "当前连接数使用率已超过80%,持续5分钟"
- alert: MySQLReplicationBroken
expr: mysql_slave_status_slave_sql_running == 0 or mysql_slave_status_slave_io_running == 0
for: 30s
labels:
severity: critical
annotations:
summary: "MySQL主从复制中断"
description: "从库 {{ $labels.instance }} 复制异常,IO线程或SQL线程不在运行状态"
- alert: MySQLSlowQueriesIncreasing
expr: increase(mysql_global_status_slow_queries[5m]) > 30
for: 10m
labels:
severity: warning
annotations:
summary: "慢查询数量激增"
description: "最近5分钟内慢查询数超过30条,请检查SQL执行情况"
for这个参数很容易被忽视。它表示异常条件必须持续多久才触发告警,作用是过滤瞬时抖动。比如连接数一下冲到90%又马上回落,这种瞬时波动可能是某个大查询瞬间占用连接,不一定是故障,设for: 5m就能避免无意义的告警轰炸。但复制中断这种严重问题,我建议for时间缩短到30s,因为主从复制中断时间越久,恢复成本越高。
Alertmanager的安装部署不再赘述,关键是配置好通知渠道。使用webhook方式与钉钉机器人的对接,在Alertmanager的配置里添加:
yaml复制route:
group_by: ['alertname']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'dingtalk'
receivers:
- name: 'dingtalk'
webhook_configs:
- url: 'http://localhost:8060/dingtalk/webhook1/send'
这里有个容易被坑的点:Alertmanager默认会做告警分组和聚合,相同告警名只发一次,而不是每条实例都发。刚开始接触时,你可能会疑惑为什么主从复制断了,只收到一条告警而不是每台从库各一条。这是设计如此,有意为之。group_by按照alertname分组,把同类型的告警合并成一条通知,避免告警风暴。group_wait是等待30秒把同组的告警打包发送,repeat_interval是重复发送间隔,4小时一条,防止告警不停刷屏。
5. 常见问题与排错经验实录
5.1 exporter无法连接MySQL的排查思路
mysql_up等于0,这是新手部署最常遇到的问题,我在前面埋了个伏笔。这个问题虽然表面简单,但根因五花八门,我按出现频率排序整理成一张速查表:
| 现象 | 常见根因 | 排查方法 |
|---|---|---|
| mysql_up = 0 | 账号密码配置错误 | 用该账号手动mysql -u mysqld_exporter -p测试连接 |
| mysql_up = 0 | .my.cnf配置路径未指定 | 确认ExecStart里--config.my-cnf指向正确的文件 |
| mysql_up = 0 | 网络不通或防火墙拦截 | telnet 目标IP 3306检查连通性 |
| mysql_up = 0 | localhost解析问题 | MySQL用户用的是@'localhost'但exporter从远端连接 |
| mysql_up = 0 | 账号权限不足 | 用root执行SHOW GRANTS确认PROCESS权限 |
其中最隐蔽的是localhost问题。MySQL用户@'localhost'只能匹配本机socket连接,如果Prometheus在另一台机器上,exporter从远端IP连接过来,这个账号会被拒绝。解决办法是替换账号为@'%',或者明确指定exporter的--mysql.address参数并配上对应的host。
排查这类问题时我的习惯是:先在exporter所在机器上手动执行mysql -u mysqld_exporter -p -h 127.0.0.1 -e 'SHOW GLOBAL STATUS;'看能否成功。如果成功,问题基本出在exporter的配置文件上;如果失败,问题在MySQL侧,要么账号没建好,要么密码不对。这一步能快速缩小问题范围,避免在无关环节浪费时间。
5.2 指标数据缺失或为空的典型原因
exporter正常启动,Prometheus能看到target为UP,但Grafana某些图表就是空的。这种“半死不活”的问题排查起来比完全宕机更伤脑筋。我遇到过的情况有这么几类:
第一类是collecter没开全。默认情况下mysqld_exporter不会开启所有collecter,比如info_schema.processlist、slave_status、engine_innodb_status都需要通过命令行参数显式开启。你在Grafana里看到的图表如果显示No data,先回到exporter的启动参数里看看对应collecter是否启用。
第二类是MySQL版本差异导致的指标缺失。MySQL 8.0里很多变量名和5.7不同,比如SHOW SLAVE STATUS在8.0里改名为SHOW REPLICA STATUS,exporter同时兼容两个版本但返回的指标名不同。如果你从旧版模板迁移过来,到8.0环境指标对不上很正常。解决办法是按新版本的实际指标名重新映射PromQL,或者直接用支持8.0的新版模板。
第三类是指标确实为0但被误判为空。比如你的MySQL从没做过全表扫描pmem,那么innodb_buffer_pool_reads就是0,这样在图上画出来的是一条横轴直线,看起来像“没有数据”。对这种情况我不是认为是异常,真相是它确实一直是0。
一些比较难排查的问题还需要结合exporter的debug日志。启动时加上--log.level=debug,能看到exporter每次抓取数据时执行了什么SQL、报了什么错。我在一次排查中发现exporter周期性地报权限不足的warning,原因是监控账号没有schema的SELECT权限,虽然多数指标正常,但performance_schema相关的指标在相当长时间内一直是空。这种问题不看日志很难定位。
5.3 存储开销与性能影响控制
Prometheus的存储问题是大部分做监控的人绕不过去的坎。数据点太多导致磁盘写满,告警变成“磁盘快满了”,监控系统最后成为新的故障源。三个维度来控制这一成本:
采集频率:全局scrape_interval不要低于15s。单机多实例场景,给MySQL job单独设置intervals:对于不重要或变化缓慢的指标可以放宽到30s甚至60s。
告警规则自身开销:rule_files里如果写了太多复杂PromQL,Prometheus每隔evaluation_interval都要执行一遍,也会消耗CPU和内存。控制在必要的几十条以内足够。
数据保留期:Prometheus的--storage.tsdb.retention.time参数控制在默认15天。如果业务需要更长期的趋势数据,建议用VictoriaMetrics或Thanos等方案做远端存储,没必要让单机Prometheus扛几个月的数据。我在一次大型项目中每天产生几百GB的监控数据,单机Prometheus扛不住就直接上了VictoriaMetrics,查询性能反而更快。
讲完了安装、采集、可视化和告警,最后分享一下我对这套监控体系后续还能做哪些扩展的想法。MySQL实例数量多的时候,可以用Consul或File Service Discovery来自动发现新实例,省去每次上线都手动改targets的麻烦。联邦集群方案用一到两台中心Prometheus去抓取其他Prometheus的聚合数据,可以解决跨机房的监控汇总问题。另外,Grafana支持配置告警的静默时间和依赖关系,比如维护窗口期的告警自动静默,这些细节能极大减少误报带来的打扰。监控系统的本质不是让人越忙,而是让运维更从容,能用数据驱动决策,不再靠经验拍脑袋。
