搞MySQL的人,谁没被慢查询折磨过?刚入行那会儿,我接了个电商系统,用户一多页面就卡成PPT,老板直接拍桌子问我是不是在跑马拉松。后来啃了两个月性能调优,才明白一个真理:SQL写得烂,再牛的硬件也救不了。你去看那些线上出问题的场景,十有八九不是MySQL扛不住,是你写的查询在裸奔。今天我就把自己踩过的坑、翻过的车,全倒出来聊聊,从索引设计到SQL写法,从慢查询排查到执行计划分析,把这些实战经验掰开了讲。别指望看完就变大神,但至少能让你下次遇到慢查询时,知道该往哪儿开枪。

先说索引,这是SQL调优的根基。很多人以为加了索引就万事大吉,结果查起来还是慢得像蜗牛爬。我见过最离谱的案例,有人给性别字段建了索引,然后查男女比例。这玩意儿选择性太差,MySQL根本懒得用,还不如全表扫描来得快。真正该建索引的,是那些出现在WHERE、JOIN、ORDER BY后面的字段,而且要注意联合索引的最左前缀原则。比如你建了(a, b, c)的联合索引,那么查询条件必须包含a才能用上,跳过去直接用b,索引就废了。我有个同事,把联合索引里的字段顺序写反了,结果一个订单查询跑了十几秒,改完索引直接降到毫秒级。所以建索引前,先想想你的查询到底是怎么写的,别让索引成为摆设。
索引不是越多越好,这个坑我掉进去过好几次。刚学会建索引那会儿,恨不得给每个字段都挂上一个,觉得这样查询肯定飞快。结果数据量一上来,写入速度慢得令人发指,因为每次插入都要更新一堆索引树。更可怕的是,索引会占用磁盘空间,一个表的数据才1GB,索引却建了3GB。后来我学乖了,只给高频查询建索引,低频查询直接走全表扫描,反正数据量小的时候也不慢。还有一个细节容易被忽略:索引字段的长度。如果你在VARCHAR(255)上建索引,MySQL会先截取前缀来排序,截取太短会导致区分度下降,太长又浪费空间。一般对字符串字段,前缀索引取20到30个字符就够用了,具体要看数据的实际分布。
说完索引,轮到SQL写法了。我见过最经典的慢查询,就是SELECT 满天飞。很多人图省事,不管用不用得上,先把所有字段拉回来,结果MySQL得去聚簇索引里捞完整行数据,传输成本翻几倍。正确的做法是只查你需要的字段,比如只查id和name,就别把那个5000字的TEXT字段也带上。还有一个常见问题:子查询嵌套过深。MySQL对子查询的优化能力有限,尤其是IN子查询,执行计划经常变成“依赖子查询”,每查一行就要跑一次子查询,数据量一上来直接崩溃。我的经验是,能JOIN就别用子查询,能EXISTS就别用IN,实在要用子查询,也尽量改成关联子查询或者用临时表来解耦。
说到JOIN,这是另一个重灾区。很多人写多表关联的时候,根本不关心驱动表和被驱动表的区别。举个例子,你有一张用户表100万行,一张订单表1000万行,如果你用小表驱动大表,MySQL会先扫描用户表,然后用用户ID去订单表里匹配,这很合理。但如果你写反了,用订单表去驱动用户表,那就要扫描1000万行数据,性能直接崩掉。所以写JOIN时,一定要把数据量小的表放在前面,同时保证JOIN条件上的字段有索引。还有一个技巧:ON条件里的字段最好跟驱动表的索引匹配,这样MySQL可以直接走索引查询,不用全表扫描。我优化过一个报表查询,就是把JOIN顺序换了一下,再把关联字段补上索引,耗时从30秒降到了0.3秒。
分组和排序也是SQL调优的重灾区。GROUP BY和ORDER BY如果处理不好,MySQL就会在内存里建临时表来排序分组,数据量大一点就直接往磁盘写,那速度比乌龟还慢。我碰到过一个案例,某系统每天跑一次统计报表,结果跑了两个多小时还没出结果。查了执行计划才发现,GROUP BY的字段没有索引,导致MySQL搞了个文件排序。后来在分组字段上建了索引,又把排序字段加进联合索引里,整个查询直接变成索引扫描,耗时降到10分钟以内。还有一个更狠的办法:如果分组统计不需要精确值,可以考虑用近似算法,比如使用COUNT(DISTINCT)时改成估算,虽然精度降一点,但速度能快几十倍。
聊聊慢查询日志和EXPLAIN,这是每个DBA的必备工具。打开慢查询日志,设置阈值到1秒,然后定时去分析。我一般用pt-query-digest这个工具,它能自动把慢查询按频率和耗时排序,一眼就能看出哪个SQL是罪魁祸首。拿到慢SQL后,用EXPLAIN看执行计划,重点关注type列:如果是ALL,说明全表扫描,必须加索引;如果是index,说明走的是索引全扫描,还有优化空间;如果是ref或者eq_ref,那就比较理想了。还要看rows列,预估扫描的行数太多,说明索引没用好。我有个习惯,每次优化完一个SQL,都去对比优化前后的rows和Extra列,确保确实有改善。别光看执行时间,有时候网络波动也会影响,看rows和type更靠谱。
写完这些,再回头看开头那个电商系统,当时我是怎么解决的?先定位到是订单查询慢,用EXPLAIN一看,发现关联字段没索引,JOIN顺序也反了。改完索引结构,把SELECT 换成只查必要字段,再把子查询改成JOIN,在分组字段上补了索引。几个改动下来,页面响应时间从12秒降到了0.8秒。老板再也没拍过桌子。MySQL优化这事儿,说穿了就是三板斧:索引设计合理、SQL写法简洁、执行计划看得懂。别想着一口气吃成胖子,每次遇到慢查询,按这个思路去排查,慢慢你就能摸到门道。下次再有人跟你说MySQL性能差,你可以笑着回一句:不是MySQL不行,是你写的SQL不行。


