MySQL 调优别急着改参数:先用四个工具把慢查询揪出来
数据库变慢的时候,绝大多数人的第一反应是去翻 my.cnf,把 innodb_buffer_pool_size 往上调。我见过不少这样的配置:一台 64G 内存的机器,缓冲池一口气设到 45G,物理内存的 70%,看着很标准,结果查询延迟没降,写入反而因为刷盘压力变得更抖。问题出在顺序——参数是最后一步,不是第一步。真正该先做的是把慢查询日志打开,看清楚到底是哪几条 SQL 在拖后腿。这篇文章不讲参数清单,讲的是四个工具该怎么排先后、各自解决什么问题,以及哪一步最值得花时间。
先开慢查询日志,别一上来就动 my.cnf
MySQL 默认不会记录慢查询,得手动开。执行 set slow_query_log='ON',再把阈值压到 set long_query_time = 1,超过一秒的语句就会被写进日志。这一步几乎零成本,但很多人跳过它,直接凭感觉改参数。
打开之后先别急着分析,用一条命令看总量:show status like 'slow_queries'。如果这个数字每分钟都在涨,说明问题很实在;如果一天下来只有个位数,那慢的可能是你的应用层,不是数据库。
日志有了,接下来才是工具上场。四款工具的分工其实很清楚,我一般按这个顺序用:
- pt-query-digest——把慢查询日志聚合成报告,看哪类 SQL 执行次数多、平均耗时高
- mysqldumpslow——MySQL 自带,快速捞出最慢的前几条,适合先看个大概
- MySQLTuner-Perl——检查配置参数是否合理,给的是参数层面的建议
- pt-index-advisor——针对具体表的查询模式,告诉你缺哪个索引、哪个索引是多余的
顺序反过来会怎样?先跑 MySQLTuner,它会告诉你 innodb_buffer_pool_size 该调大、key_buffer_size 该调小,你照着改了,重启数据库,慢查询一条没少。因为慢的根因是某条 SQL 扫了 80 万行,不是缓冲池不够大。
pt-query-digest 的报告,先看 Rows_examined 而不是 Query_time
安装不复杂。Debian 或 Ubuntu 上 sudo apt-get install percona-toolkit,CentOS 或 RHEL 上换成 yum 就行。假设慢查询日志在 /var/log/mysql/slow-query.log,直接跑:
pt-query-digest /var/log/mysql/slow-query.log
输出是一份按总耗时排序的报告,包含执行次数、平均执行时间、最大执行时间,还有查询示例。多数人第一眼会盯 Query_time,我建议先看 Rows_examined——扫描行数。
一条 SQL 平均跑 2 秒、每次扫 50 万行,和另一条跑 3 秒、每次只扫 200 行,前者是索引问题,后者可能是锁等待或者返回数据量太大。两者的修法完全不同。Rows_examined 高得离谱的,基本可以断定索引没走对,直接进下一步用 pt-index-advisor 看。
这里有个取舍:报告里执行次数少但单次极慢的 SQL,值得优化,但收益有限;执行次数上万、单次几百毫秒的,加起来才是大头。我的判断是先把「次数 × 平均耗时」这个乘积最高的那几条干掉,通常三五条就能砍掉一半以上的总耗时。
加索引之前,先让 pt-index-advisor 算一遍
这是整个流程里最值得花时间的一步,也是最容易踩坑的一步。

Percona Toolkit 里包含 pt-index-advisor,用法是连上库指定表:pt-index-advisor --user=your_user --password=your_password --host=your_host mydb.mytable。它会分析这张表的查询模式,给出该加的索引和该删的索引。
为什么不能凭经验直接加?因为索引不是白给的。每加一个索引,写入时就要多维护一棵 B+ 树。一张写多读少的表,索引加多了,INSERT 和 UPDATE 的开销会明显上升。我见过一张日志表被加了七个索引,查询是快了,但写入吞吐掉了将近四成,最后又回头删掉三个。
pt-index-advisor 的价值就在于它是按实际查询模式算的,不是按「字段看起来像会被查」来猜。它给出建议后,还要结合业务判断:如果某个索引只服务一个月跑一次的报表查询,那不值得为它牺牲日常写入性能。
顺带提一句,Percona Toolkit 里还有个 pt-table-checksum,用来校验主从表数据一致性。大量数据操作之后跑一次,能提前发现主从不同步的问题,避免某天切库时才发现数据对不上。这个不属于性能调优,但属于数据库运维里该有的习惯。
MySQLTuner-Perl 和 mysqltuner-php:参数建议怎么读
把 SQL 和索引的问题处理完,再来看参数。这时候 MySQLTuner 给的建议才有意义。
MySQLTuner-Perl 基于 Perl,下载脚本后 chmod +x mysqltuner.pl,然后 ./mysqltuner.pl 就能跑,默认连本地 root,也可以用 -u 和 -p 指定账号。它会输出一堆参数建议,其中最常被引用的是 innodb_buffer_pool_size 设为物理内存的 70% 到 80%。
这个比例要打个问号。如果这台机器上还跑着别的服务,比如 Web 应用或者缓存,缓冲池吃掉 80% 内存,剩下的进程就得靠 swap 活着,整体反而更慢。稳妥的做法是留出至少 4G 给系统和其他进程,剩下的再给缓冲池。64G 的机器,我一般让缓冲池停在 40G 到 48G 之间,而不是硬套 70%。
如果还在用 MyISAM 引擎,key_buffer_size 是另一个要看的参数。不过现在新项目基本都上 InnoDB 了,MyISAM 更多出现在历史遗留的表上,调它的优先级不高。
mysqltuner-php 是同一类工具的 Web 版,PHP 环境加 Nginx 或 Apache,把代码放到 Web 根目录,编辑 config.php 填上数据库连接信息,浏览器访问就能看到运行状态和建议。它的好处是直观,连接数、查询缓存命中率这些指标有图表。但要注意,MySQL 8.0 已经移除了查询缓存,所以 Qcache_hits 和 Qcache_lowmem_prunes 这两个指标在 8.0 上根本不存在。如果你在 8.0 上还盯着查询缓存调,方向就错了。这两个工具适合做日常巡检,不适合当成调优的唯一依据。
参数改完,硬件和架构的顺序别搞反
工具能帮你找到问题,但有些瓶颈工具解决不了。

慢查询日志里如果出现大量排序操作,Extra 字段显示 using filesort,说明排序没走索引。这种情况加索引能解决一部分,但如果数据量本身到了千万级,排序需要的临时空间会很大,tmp_table_size 和 sort_buffer_size 调大只是缓解,根治还得从查询写法或者分表入手。
执行计划里的 type 字段也值得看。出现 all 就是全表扫描,ref 是命中非唯一索引,const 是命中主键或唯一索引。从 all 变成 ref 或者 range,通常就是加对了一个索引的事。
再往上走,单机调优的天花板是会到的。读写分离、分库分表这些手段,本质上是在单机扛不住的时候用架构换性能。但这一步的成本不低——分库分表意味着应用层要改路由逻辑,读写分离要处理主从延迟带来的数据一致性问题。我的建议是:单表数据量没到千万级、QPS 没到几千,先把索引和 SQL 写对,别急着上架构。
硬件层面,数据库对内存和磁盘 IO 最敏感。如果缓冲池已经给足、索引也加对了,查询还是慢,那可能是磁盘 IOPS 到顶了。这种情况换 SSD 或者把数据放到 IO 更快的机器上,比继续调参数有效。秀米云的 马来西亚裸机云 XI 配的是 E5-2680v4 双路、64G 内存、20M 带宽,月付 $159.00,64G 内存跑一个中等规模的 MySQL 实例,缓冲池留 40G 左右比较从容。如果数据量更大、内存需求更高,马来西亚服务器5 用 Platinum-8168 双路加 64G 内存,月付 $469.00,CPU 单核性能更好,适合排序和聚合比较重的场景。机房在吉隆坡,选之前先确认你用户的访问延迟能不能接受。
该先做什么,不该先做什么
回到最开始那个问题:一台机器变慢了,动手顺序是什么。
开慢查询日志,用 pt-query-digest 聚合,按 Rows_examined 排序找出最该优化的几条 SQL,用 pt-index-advisor 验证索引方案,改完再看 MySQLTuner 的参数建议。这个顺序里,前两步几乎不花钱,第三步要花时间判断,第四步才涉及重启和配置变更。
不该先做的:一上来改 innodb_buffer_pool_size,一上来加索引,一上来考虑分库分表。这三件事都有成本,而且做错了要回滚。参数调错了重启就行,索引加错了删除也快,但分库分表改错了,回滚的代价是几个通宵。
如果你手上只有一台 32G 到 64G 内存的机器,跑的是几千 QPS 以内的业务,把这四个工具用顺,慢查询压下去,基本就够了。等到工具都调不出明显问题、机器资源却还是吃紧,那时候再谈换硬件或者改架构,钱才花在刀刃上。