深夜两点,手机突然震动。生产环境的报警群炸开了锅:“订单服务接口超时”、“数据库连接数打满”。运维兄弟在群里发了一句“卡死了”,紧接着就是此起彼伏的“谁在查库?”、“是不是大事务?”。
这时候,如果你还在用 top 看CPU,用 mysql -e "show processlist" 手动翻日志,那基本就是在浪费时间,等着被老板骂吧。今天咱们不聊虚的,直接把MySQL监控里的“五大金刚”——Percona Monitoring and Management (PMM)、Zabbix、以及配合使用的 pt-query-digest、sys_schema 和 performance_schema——拆开来揉碎了讲。重点就三个痛点:慢查询定位、死锁分析、CPU飙升。
第一关:Percona PMM —— 可视化界的“透视眼”
说实话,刚接触MySQL监控的时候,我是不屑于用工具的,觉得看数据不如看日志实在。直到有一次,一个奇怪的CPU波动持续了半小时,PMM的图表直接把问题暴露无遗——某张表在凌晨3点被全表扫描了。那一刻我才知道,“看见”比“思考”重要一万倍。
PMM(Percona Monitoring and Management)是Percona开源的监控平台,它不是简单的Zabbix封装,而是专门为MySQL、MongoDB、PostgreSQL设计的。它的核心优势在于收集粒度细和查询分析深。
1.1 安装与部署:别怕麻烦,一次投入长期省心
部署PMM其实不难,官方提供了Docker镜像。如果你是在内网环境,建议直接在一台Linux服务器上跑:
# 创建pmm容器
docker run -d \
--name pmm-server \
--restart always \
-v /opt/consul-data:/usr/share/consul \
-v /opt/pmm-data:/var/lib/grafana \
-p 80:80 \
-p 443:443 \
percona/pmm-server:2
# 启动成功后,访问 https://<你的服务器IP>,默认账号 admin/admin
然后在被监控的MySQL服务器上安装客户端:
# CentOS/RHEL 示例
yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
percona-release enable-only pmm-client release
yum install -y pmm-client
# 连接PMN服务器
pmm-admin config --server-insecure-tls --server-url=https://admin:admin@<PMM服务器IP>
# 添加MySQL监控
pmm-admin add mysql --user=root --password=your_password
1.2 如何用它快速定位“慢查询”?
PMM最强大的地方是QAN(Query Analytics)。很多开发者以为开了slow_log就完了,其实QAN能告诉你:哪个SQL占用时间最长?哪个SQL扫描行数最多?哪个SQL频率最高?
在PMM界面的“Query Analytics”中,你可以看到所有慢查询的排序。比如,你发现有一个SELECT * FROM orders WHERE status = 0排第一,耗时5秒。点击它,PMM会直接给你展示这个SQL的执行计划(Explain),甚至能看到它在不同时间段的资源消耗趋势。
真实案例:有一次,PMM显示某个INSERT语句频繁触发,导致MySQL线程创建开销过大。通过QAN的“Schema Analytics”找到具体表和字段,发现是应用层没有批量插入,而是在循环里单条INSERT。改成批量后,CPU直接从80%降到20%。
1.3 CPU飙升?看“OS Metrics”和“MySQL Top”
PMM的Dashboard里有个“MySQL Overview”面板。如果CPU飙升,不要只看总数,要看Per-User和Per-Database。PMM会自动把CPU消耗按连接用户和数据库拆分。你一眼就能看出,是不是某个业务线(比如报表系统)把数据库拖垮了。
第二关:Zabbix —— 企业级监控的“压舱石”
如果说PMM是专门给DBA看的“透视眼”,那Zabbix就是给整个运维团队看的“仪表盘”。很多公司已经有Zabbix体系了,这时候不要把PMM和Zabbix割裂开,而是要让它们联动。
2.1 为什么有了PMM还要Zabbix?
PMM的告警机制相对简单,而Zabbix的优势在于强大的告警触发器和多系统联动。比如,你可以设置:当MySQL的QPS超过阈值,且Zabbix检测到应用服务器的响应时间变长时,自动触发一个P1级故障单。这种跨维度的告警,PMM单独搞不定。
2.2 Zabbix监控MySQL的关键模板
Zabbix官方提供了MySQL模板,但你需要自定义几个关键指标。对于排查卡顿,以下三个Item是必配的:
Threads_connected vs Threads_running:
Threads_connected是总连接数,Threads_running是活跃连接数。如果这两个数接近,说明数据库忙不过来了,连接都在排队。- 经验值:如果
Threads_running长期接近max_connections,基本就是崩了的前兆。
Slow_queries:
- 每分钟新增的慢查询数量。这是最直接的“卡顿”信号。
Innodb_row_lock_waits:
- 行锁等待次数。如果这个值短时间内飙升,说明有并发写入冲突,很可能就是死锁或锁竞争导致的CPU飙升。
2.3 如何用Zabbix快速发现“隐藏”的慢查询?
Zabbix本身不解析SQL,但它能告诉你“什么时候”有慢查询。你可以创建一个聚合Item:“过去5分钟内慢查询总数 > 10”。一旦触发,结合PMM的QAN,就能立即定位到具体的SQL。
实战技巧:在Zabbix里配置一个“触发器”,当mysql.status[Slow_queries]在1分钟内增长超过50时,发送微信/钉钉告警,并附带当前时间点的show status like 'Thread%'截图。这样,当你收到告警时,已经掌握了第一手数据。
第三关:pt-query-digest —— 慢日志的“解剖刀”
PMM和Zabbix告诉你“出事了”,而pt-query-digest告诉你“谁干的,怎么干的”。这是Percona Toolkit里的神器,几乎是MySQL性能排查的标配。
3.1 基本原理
MySQL的slow_log里存的是原始SQL,里面可能混着大量重复的查询。pt-query-digest会把这些SQL进行归类、去重、统计分析,最终生成一份报告。
3.2 实战:如何用它定位CPU飙升?
假设你的MySQL CPU突然飙到100%,你首先导出慢查询日志,然后运行:
pt-query-digest /var/log/mysql/slow.log --since "10 minutes ago"
输出报告解读:
报告最上面会有一个Rank部分,列出占用时间最长的SQL。重点看Query_time和Lock_time。
场景A:CPU高,但
Lock_time低,Rows_sent高。- 结论:这是典型的计算密集型查询,可能在做大表扫描或复杂的Join。
- 行动:检查这个SQL的执行计划,看是否缺少索引。
场景B:CPU高,
Lock_time也高。- 结论:这是锁竞争导致的CPU浪费。线程在等待锁,但CPU并没有闲着(自旋锁等)。
- 行动:结合下面的死锁分析章节。
3.3 进阶:直接分析当前运行中的查询
如果慢日志还没生成(比如查询时间不够长,没达到long_query_time阈值),你可以用--processlist选项直接分析当前连接的查询:
pt-query-digest --processlist --user=root --password=xxxx --interval=5
这个命令每5秒抓取一次进程列表,适合抓那些“刚好卡在临界值”的诡异慢查询。
第四关:performance_schema —— 深入骨髓的“CT扫描”
当PMM、Zabbix和pt-query-digest都看不出来问题时,不要慌,还有performance_schema(PS)。这是MySQL 5.5+引入的,以前默认是关闭的,现在MySQL 8.0默认开启。它记录了数据库底层的所有开销信息。
4.1 为什么它能解决前三个工具解决不了的问题?
慢查询日志只记录超过阈值的SQL,而PS记录的是每一次事件,包括:
- 每个语句的等待事件(File I/O, Network I/O, Lock)
- 每个阶段的耗时(Parsing, Sending data, Copy to tmp table)
- 每个会话的资源消耗
4.2 如何定位“CPU空转”或“锁等待”?
很多CPU飙升并不是因为SQL在计算,而是因为线程在等待。比如,等待磁盘I/O,或者等待行锁。
使用sys库(基于PS的封装,更易读):
-- 查看当前等待最激烈的SQL
SELECT * FROM sys.session WHERE wait_state IS NOT NULL ORDER BY timer_wait DESC LIMIT 10;
-- 查看哪个表锁竞争最严重
SELECT * FROM sys.schema_table_lock_waits;
真实案例:有一次,CPU使用率很高,但slow_log里没什么慢查询。用sys.session一查,发现大量线程卡在wait/synch_mutex/src/row0lock.cc,也就是InnoDB的行锁等待。原来是有个大事务在修改主键,而其他事务在并发更新这些行。通过PS找到了锁的源头,直接定位到了那个长事务,kill掉后CPU瞬间恢复正常。
4.3 关键视图推荐
sys.statements_with_runtimes_in_95th_percentile:找出95%分位耗时最长的SQL。sys.hosts_io_by_type:查看哪个主机(应用服务器)产生的I/O最多。sys.memory_global_total:检查内存使用,防止OOM。
第五关:死锁与锁竞争的“终极检测”
死锁是MySQL排查中最头疼的问题之一,因为它发生快、消失也快,往往只留下一个错误日志。
5.1 如何快速发现死锁?
不要等应用报错,要在发生前发现迹象。
- Zabbix监控:监控
Innodb_deadlocks状态变量。如果这个值不增,但Innodb_row_lock_time(锁等待总时间)在增加,说明有严重的锁竞争,快死锁了。 - PMM的“MySQL Locks”面板:PMM专门有一个Locks图表,能直观看到Lock Wait和Deadlocks的趋势。
SHOW ENGINE INNODB STATUS:这是最原始但最有效的方法。发生死锁后,立刻执行这个命令,最后一段LATEST DETECTED DEADLOCK会详细告诉你两个事务分别持有哪把锁,又等待哪把锁。
SHOW ENGINE INNODB STATUS\G
在输出的LATEST DETECTED DEADLOCK部分,你会看到类似这样的结构:
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `db`.`order`
trx id 1000001 lock mode S locks rec but not gap waiting
这能告诉你,事务1在等order表的某一行加读锁,而事务2已经持有了这把锁。
5.2 如何分析和预防?
- 分析:结合
pt-query-digest和performance_schema.events_waits_current,找出导致死锁的两个SQL,看看它们的执行顺序是否冲突。 - 预防:
- 统一访问顺序:确保所有事务以相同的顺序访问表/行。
- 缩小事务范围:不要把非数据库操作(如调用外部API)放在事务里。
- 使用较低隔离级别:如果业务允许,使用READ-COMMITTED可以减少间隙锁(Gap Lock)的使用,从而减少死锁概率。
实战总结:一套组合拳的流程
当老板问你“数据库为什么卡了”,不要慌,按这个流程来:
- 看PMM Dashboard:确认是CPU高、IO高还是连接数高?是哪个数据库、哪个用户的问题?
- 看Zabbix告警:确认慢查询数量、锁等待次数是否激增?
- 跑pt-query-digest:获取过去半小时的慢查询分析报告,找出Top 10耗时SQL。
- 查performance_schema/sys视图:如果没慢查询但CPU高,查当前等待事件,看是锁等待还是I/O等待。
- 查InnoDB Status:如果怀疑死锁,立刻
SHOW ENGINE INNODB STATUS。
最后给个小建议:监控工具只是手段,建立基线才是关键。你要知道你的MySQL在正常情况下的QPS是多少,CPU使用率是多少。只有知道“正常”,才能发现“异常”。
希望这套组合拳能帮你在面对MySQL卡顿时,不再手足无措。毕竟,作为运维人,我们的目标不是“救火”,而是“防火”。
