说实话,刚入行那会儿我遇到MySQL变慢,第一反应就是瞎猜:是网络卡了?是代码写得烂?还是数据库配置有问题?结果排查了一整天,最后发现只是少加了一个索引。那种无力感,相信很多开发者都懂。今天咱们不整那些虚头巴脑的理论,直接上手,从最基础的慢日志开启,到Percona Monitoring Plugins(PMP)的实战配置,手把手教你把数据库性能这个“黑盒”打开,让你也能像老运维一样,一眼看出谁在拖后腿。
第一步:别急着装工具,先看看MySQL自带的“黑匣子”
很多人一上来就想装监控平台,其实MySQL自己早就备好了“行车记录仪”,那就是慢查询日志(Slow Query Log)。如果你连这个都没开,后面所有的神器都是废的。
为什么慢日志是根基?
想象一下,你要抓小偷,但现场连监控都没有,你怎么破案?慢日志就是你的监控探头。它会记录执行时间超过阈值的SQL语句,以及全表扫描的查询。没有它,你连问题在哪都不知道,更别说用PMP分析了。
如何优雅地开启慢日志?
别去改配置文件重启(虽然那也可以),我们来看看怎么用变量动态开启,这样更安全,也能立即生效。
打开你的MySQL命令行客户端,执行以下SQL:
-- 查看当前慢查询日志状态
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 开启慢查询日志(动态修改,无需重启)
SET GLOBAL slow_query_log = 'ON';
-- 设置日志文件路径(建议放在数据目录下,权限要正确)
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';
-- 设置阈值,这里设为1秒。生产环境建议0.5秒到2秒之间,视业务敏感度而定
SET GLOBAL long_query_time = 1;
-- 可选:记录没有使用索引的查询(这个很关键!)
SET GLOBAL log_queries_not_using_indexes = ON;
关键点解析:
long_query_time:默认是10秒,太长了!大部分慢查询都在1秒以内,但累积起来就是性能杀手。建议设为1或更低。log_queries_not_using_indexes:这个开关是我的最爱。它专门抓那些“没走索引”的查询,哪怕执行很快,也可能意味着数据量增长后会出问题,或者逻辑有误。开启它,能让你的慢日志更有价值。- 文件路径:
/var/lib/mysql/slow.log是常见路径,但具体取决于你的安装方式和datadir配置。确保MySQL用户对这个目录有写权限。
验证是否生效
执行完后,立刻查一下:
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';
确认slow_query_log为ON,long_query_time为你设置的值,log_queries_not_indexes为ON。然后,你可以故意跑一条慢查询来测试:
-- 假设你有个user表,没有对email字段建索引
SELECT * FROM user WHERE email = 'test@example.com';
等一会儿,去/var/lib/mysql/slow.log看看,应该能看到这条记录。
第二步:手动分析慢日志——初学者的“侦探游戏”
开了慢日志,文件会越来越大。别慌,我们先学会用命令行工具mysqldumpslow来提炼精华。这比直接看几十MB的日志文件要高效得多。
常用命令示例
# 按查询次数排序,显示最频繁执行的慢查询(Top 10)
mysqldumpslow -s c -t 10 /var/lib/mysql/slow.log
# 按平均执行时间排序,显示最耗时的慢查询(Top 10)
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log
# 按锁定时间排序,找出锁等待严重的查询
mysqldumpslow -s l -t 10 /var/lib/mysql/slow.log
# 更详细的输出,包含实际SQL(注意:可能会很长)
mysqldumpslow -s t -t 10 -g 'SELECT' /var/lib/mysql/slow.log
参数解读:
-s:排序方式。c(count),t(time),l(lock time),r(rows)。-t:显示前N条。-g:正则过滤,比如只看SELECT语句。
案例:从慢日志中发现“索引失效”的蛛丝马迹
假设你运行mysqldumpslow -s t -t 10 slow.log,看到这样一条记录:
Count: 1
Time: 5.02s (5s)
Lock: 0.00s (0s)
Rows: 1000000.0 (1000000)
SELECT * FROM orders WHERE create_time > '2023-10-01';
分析:
- 执行时间5秒:明显慢。
- Rows 100万:这意味着MySQL扫描了100万行数据才返回结果。如果这张表总共就100万行,那就是全表扫描!
- 问题:
create_time字段很可能没有索引,或者索引没有被正确使用。
下一步行动: 去数据库里检查表结构:
SHOW CREATE TABLE orders;
如果create_time没有索引,那就创建一个:
ALTER TABLE orders ADD INDEX idx_create_time (create_time);
再次执行查询,对比执行时间,你会发现奇迹般地变快了。这就是慢日志的威力——它让你有据可依,而不是瞎猜。
第三步:引入Percona Monitoring Plugins(PMP)——让监控变得“可视化”
手动看日志终究是笨功夫。当服务器多了、SQL种类多了,你需要一个更强大、更直观的监控平台。Percona Monitoring Plugins(PMP)就是为此而生。它不是一个单一的监控软件,而是一套基于Prometheus和Grafana的监控方案,专门为MySQL优化。
为什么选PMP?
- 专为MySQL设计:相比通用的监控工具,PMP更懂MySQL的内部指标,比如InnoDB buffer pool命中率、行锁等待、慢查询分布等。
- 开源免费:Percona是MySQL生态里的老牌子,社区活跃,文档丰富。
- 与现有生态集成好:Prometheus + Grafana是现在云原生监控的事实标准,PMP让你轻松接入。
实战部署:Docker一键启动
为了简化,我们用Docker Compose来搭建一套包含MySQL、Exporter、Prometheus和Grafana的完整监控环境。
在你的工作目录下,创建一个docker-compose.yml文件:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql_prod
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./slow.log:/var/lib/mysql/slow.log # 挂载慢日志,方便Exporter读取
command: --slow-query-log=1 --long-query-time=1 --log-queries-not-using-indexes=1
mysqld_exporter:
image: percona/percona-mysql-exporter:latest
container_name: mysqld_exporter
ports:
- "9104:9104"
environment:
DATA_SOURCE_NAME: 'exporter:exporter@tcp(mysql:3306)/myapp'
depends_on:
- mysql
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.listen-address=0.0.0.0:9090'
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin123
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on:
- prometheus
volumes:
mysql_data:
prometheus_data:
grafana_data:
再创建一个prometheus.yml,告诉Prometheus从哪里抓取数据:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mysql'
static_configs:
- targets: ['mysqld_exporter:9104']
启动它们:
docker-compose up -d
接入Grafana仪表盘
- 打开浏览器,访问
http://localhost:3000,用admin/admin123登录。 - 进入“Connections” -> “Add new connection” -> 选择“Prometheus”,URL填
http://prometheus:9090。 - 导入Percona官方提供的MySQL仪表盘。你可以从Grafana Dashboard搜索“Percona MySQL”或“MySQL by Percona”。选择一个高评分的,比如ID为11323的“MySQL by Percona”。
- 导入后,你会看到一系列精美的图表:连接数、QPS/TPS、InnoDB缓冲池命中率、慢查询数量、锁等待时间等。
第四步:实战排查——索引失效与锁等待
现在,监控平台搭好了,让我们用真实场景来演练如何排查问题。
场景一:索引失效,查询变慢
现象:Grafana上某个时间片,QPS没有明显下降,但平均查询时间飙升。同时,慢日志数量增加。
排查步骤:
- 查看慢日志:用
mysqldumpslow -s t -t 10 slow.log找出最慢的几条SQL。 - 使用EXPLAIN分析:拿到那条慢SQL,比如:
执行:SELECT * FROM orders WHERE status = 'pending' AND create_time > '2023-10-01';EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND create_time > '2023-10-01'; - 解读EXPLAIN结果:
type列:如果是ALL,表示全表扫描,问题严重。key列:如果是NULL,表示没有用到索引。rows列:如果这个数字很大,接近表总行数,确认是全表扫描。
- 检查索引定义:
看看SHOW INDEX FROM orders;status和create_time上是否有索引。即使有,也要看是不是复合索引,以及索引的顺序是否正确。 - 可能的问题:
- 没有索引:那就
ALTER TABLE ... ADD INDEX。 - 索引列上有函数运算:比如
WHERE YEAR(create_time) = 2023,这会导致索引失效。应该改成范围查询:WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01'。 - 隐式类型转换:如果
status是字符串类型,但查询时传入了数字,MySQL会进行类型转换,导致索引失效。确保查询条件类型与字段类型一致。
- 没有索引:那就
Grafana辅助:在PMP仪表盘中,查看“Index Hit Rate”(索引命中率)图表。如果命中率下降,也提示索引可能失效或被误用。
场景二:锁等待,事务阻塞
现象:应用日志出现数据库连接超时错误。Grafana上“Lock Wait Time”图表出现尖峰。
排查步骤:
- 查看当前锁等待:
或者在MySQL 8.0+中,更推荐使用:SELECT * FROM performance_schema.data_lock_waits;
这会显示谁在等待,谁在持有锁。SELECT * FROM sys.innodb_lock_waits; - 找到阻塞源:从上面的查询结果中,找到
BLOCKING_THREAD_ID或BLOCKING_ENGINE_TRANSACTION_ID,然后去查这个事务当前的状态:
看看这个事务正在执行什么SQL,已经运行了多久。SELECT * FROM information_schema.innodb_trx; - 分析原因:
- 长事务:有没有事务打开了很久没有提交?这会导致锁无法释放。检查应用代码,看看是否有事务包围了过多逻辑,或者漏掉
commit/rollback。 - 大批量操作:是否在高峰期执行了大表的
DELETE、UPDATE或INSERT?这些操作会持有行锁或间隙锁,阻塞其他事务。 - 索引缺失:同样,如果
UPDATE或DELETE语句没有走索引,MySQL可能会锁住更多行,甚至表锁,影响其他请求。
- 长事务:有没有事务打开了很久没有提交?这会导致锁无法释放。检查应用代码,看看是否有事务包围了过多逻辑,或者漏掉
- 紧急处理:如果阻塞严重影响业务,可以考虑
KILL掉那个阻塞的事务(谨慎操作!):
但记住,这只是治标,根本还是要优化SQL和事务逻辑。KILL <thread_id>;
Grafana辅助:PMP仪表盘中有“Lock Wait”相关的图表,可以直观看到锁等待的平均时间和最大时间。配合慢日志中Lock Time字段的分析,可以快速定位锁问题。
第五步:进阶技巧——让监控更智能
1. 告警配置
光看不够,出问题得第一时间知道。Prometheus配Alertmanager,Grafana也能配告警。
在Grafana中,为关键指标设置告警规则,比如:
- 慢查询数量在5分钟内超过100条。
- 平均查询时间超过2秒。
- 锁等待时间超过1秒。
- InnoDB缓冲池命中率低于95%。
告警可以通过邮件、Slack、钉钉等渠道发送。
2. 慢日志的定期轮转
慢日志会持续增长,需要及时清理,否则磁盘会爆。
可以使用mysqladmin flush-slow-log来清空日志并生成新的日志文件,但这会中断监控数据。更好的方式是使用logrotate或者Percona Toolkit中的pt-kill配合脚本,定期归档和压缩旧的慢日志。
3. 结合业务指标
数据库性能不能孤立看。把MySQL的QPS、慢查询数等指标,与应用的服务端指标(如CPU、内存、请求延迟)关联起来分析,才能更准确地定位问题根源。是数据库慢,还是应用处理数据慢?是代码逻辑问题,还是数据库资源瓶颈?
结语:监控是手段,优化是目的
从手动开启慢日志,到部署Percona Monitoring Plugins,我们搭建的不仅仅是一套监控工具,更是一套发现问题、分析问题的方法论。记住,工具再强大,也需要人去解读数据。
- 慢日志是基石:没有它,一切都是空中楼阁。
- EXPLAIN是利器:面对慢SQL,先
EXPLAIN,再动手优化。 - 监控是眼睛:PMP让你看清数据库的“健康状况”,提前发现潜在风险。
- 业务是根本:所有优化都要围绕业务需求,不能为了优化而优化,导致功能损坏。
希望这篇实战指南能帮你从MySQL性能问题的“受害者”变成“掌控者”。数据库性能优化是一场持久战,没有一劳永逸的方案,只有持续监控、持续分析、持续优化的过程。祝你排查顺利,数据库飞速运行!
