别瞎改SQL了,用EXPLAIN一眼看透性能瓶颈

别瞎改SQL了,用EXPLAIN一眼看透性能瓶颈 别瞎改SQL了用EXPLAIN一眼看透性能瓶颈你有没有过这种时刻对着一条慢SQL改了一下午加了好几个索引测试环境跑起来挺快一上线性能反而更差了。你翻遍了SQL的每一个字符怎么都找不到问题出在哪最后只能对着监控里飙升的CPU使用率发呆。其实你离真相只差一步——从来没有真正读懂过EXPLAIN返回的执行计划。我刚做开发的时候也把EXPLAIN当成一个“点一下看看”的工具扫一眼type字段不是ALL就觉得没问题根本没仔细看过每一列的细节。直到有一次线上出了故障一条SQL明明建了索引却莫名其妙走了全表扫描我对着EXPLAIN输出盯了十分钟才发现Extra列里藏着“Using index condition”之外的关键信息瞬间就定位到了隐式类型转换的坑。从那之后我才明白EXPLAIN不是一个简单的检查工具它是MySQL给我们打开的“上帝视角”能让你直接看到优化器心里到底在想什么。今天这篇文章我就把沉淀了多年的EXPLAIN实战经验全部讲透从每一列的含义到真实案例的对比分析帮你彻底掌握这个SQL优化的“终极武器”。一、先搞懂EXPLAIN的核心输出列别再只看type字段很多人用EXPLAIN只会扫一眼type是不是ALL觉得只要避免了全表扫描就算优化完成了。但实际上EXPLAIN的输出有十几个关键字段每一个字段都藏着优化器的关键决策信息漏掉任何一个你都可能错过真正的性能瓶颈。我把日常工作中最核心的几个字段结合真实案例给你拆解清楚。第一个最容易被忽视的关键字段是‌type‌它代表了MySQL找到目标行的访问方式性能从差到好有完整的层级。很多人只知道ALL是全表扫描却不知道不同的type之间性能差距能达到上千倍。ALL是最差的情况代表全表扫描遍历整张表的所有数据行百万级数据下这种访问方式基本就是灾难。比ALL好一点的是index代表遍历整个索引树虽然不用扫描全部数据行但要把整个索引从头到尾扫一遍性能还是很差。接下来是range代表索引范围扫描常见于、、IN、BETWEEN这类查询条件这是大部分生产环境中合理查询的最低标准。再往上是ref代表非唯一性索引的等值查询比如普通二级索引的等值匹配这种访问方式性能已经很不错了。更好的是eq_ref代表关联查询时使用主键或者唯一索引做等值匹配每次关联只能返回一行数据是关联查询里最理想的访问方式。性能最好的是const和system代表通过主键或者唯一索引直接匹配一行数据优化器在查询阶段就能把这行数据转换成常量这种访问方式几乎没有任何开销。我之前遇到过一个开发同学写的查询type字段显示是range他就觉得已经优化到位了但实际上这条SQL的访问方式完全可以升级到ref。原来的查询条件是WHERE create_time 2024-01-01用的是普通的范围扫描后来业务调整成只查询某一天的数据把条件改成了create_time 2024-01-01type直接从range变成了ref扫描行数从十万行降到了一千行性能提升了一百倍。很多时候你离最优性能只差一个把范围查询改成等值查询的小调整。第二个核心字段是‌key‌和‌key_len‌。key字段显示的是MySQL最终实际选择使用的索引很多时候你给表建了好几个索引你以为它会用你设计的那个最优索引但优化器可能选了另一个甚至直接放弃索引走全表扫描这时候看key字段就能立刻发现问题。而key_len字段是很多人完全不知道怎么用的“宝藏字段”它代表了执行计划里使用的索引字节长度。通过这个长度你可以精确计算出联合索引里到底用到了几个字段有没有出现索引截断的情况。举个例子你建了一个联合索引(a INT, b INT, c INT)每个INT类型占4字节如果执行计划里key_len的值是4说明这个查询只用到了联合索引里的第一个字段a如果key_len是8说明用到了a和b两个字段如果key_len是12说明三个字段全部用到了。我之前排查过一个联合索引失效的问题建了(a, b, c)的索引但查询性能一直不好看key_len发现只有4说明只用到了a字段。仔细检查SQL才发现b字段的查询条件后面跟了一个OR导致联合索引的后续字段完全无法被利用调整SQL写法之后key_len变成了12查询性能直接提升了五倍。这个小技巧不需要看SQL的具体条件光靠key_len的数值就能判断联合索引的利用率非常实用。第三个最容易被误解的字段是‌Extra‌这个字段里的信息是整个执行计划的精华很多隐藏的性能问题只有在Extra里才能发现。很多人只知道Using index代表覆盖索引性能很好但不知道还有很多其他关键标识每一个都对应着不同的性能问题。比如Using filesort代表MySQL拿到数据之后需要在内存或者磁盘里做额外的排序操作这种操作的开销非常大数据量大的时候很容易把CPU打满。Using temporary代表MySQL创建了临时表来保存中间结果常见于GROUP BY、DISTINCT、多表关联之后的排序场景临时表的读写开销往往是慢查询的根源。Using where代表存储引擎返回数据之后Server层还要再做一次过滤说明索引的过滤性不够好返回了大量不需要的数据。Using index condition代表开启了索引条件下推MySQL会在存储引擎层先过滤掉不符合条件的数据减少回表的数量这是MySQL 5.6之后引入的优化特性。我之前优化过一个GROUP BY的慢查询EXPLAIN的type是ref看起来访问方式没问题但Extra里显示Using temporary和Using filesort查询执行了十多秒。后来我给GROUP BY的字段建了联合索引让分组操作可以直接利用索引的有序性完成优化之后Extra里的Using temporary和Using filesort直接消失了查询时间降到了几十毫秒。很多人优化SQL只盯着type字段完全忽略Extra里的临时表和文件排序花了大量时间调整索引性能却没有任何提升就是这个原因。二、三组真实EXPLAIN对比看懂优化前后的本质差异光讲理论很抽象我拿出三组我在真实线上环境中记录的EXPLAIN对比案例把优化前后的执行计划完整展示出来让你直观看到每一步优化带来的执行计划变化彻底搞懂优化的逻辑。第一组对比是最常见的全表扫描优化成覆盖索引的场景。优化前的SQL是查询某个用户的所有已支付订单的总金额当时表上没有任何二级索引原始的EXPLAIN输出是这样的表格id select_type table type possible_keys key key_len ref rows Extra1 SIMPLE orders ALL NULL NULL 0 NULL 5200000 Using where从这个执行计划里可以看到type是ALL全表扫描五百二十万行数据key字段是空的没有用到任何索引Extra里的Using where代表Server层要对全表扫描出来的所有数据做过滤这条SQL的执行时间超过了十五秒。优化之后我们新建了联合覆盖索引(user_id, order_status, order_amount)调整后的EXPLAIN输出变成了这样表格id select_type table type possible_keys key key_len ref rows Extra1 SIMPLE orders ref idx_user_status_amt idx_user_status_amt 8 const,const 126 Using index优化之后的变化非常明显type从ALL变成了ref访问方式从全表扫描变成了等值索引匹配key字段显示使用了新建的联合索引key_len是8说明用到了user_id和order_status两个字段扫描行数从五百二十万降到了一百二十六直接减少了四万多倍Extra里的Using where变成了Using index说明完全走覆盖索引不需要回表Server层也不需要再做额外过滤。优化之后这条SQL的执行时间从十五秒降到了两毫秒性能提升了七千多倍。第二组对比是大分页OFFSET优化的场景。优化前的SQL是LIMIT 100000, 20原始的EXPLAIN输出是这样的表格id select_type table type possible_keys key key_len ref rows Extra1 SIMPLE orders index NULL idx_ctime 5 NULL 1000020 Using where; Using filesort这个执行计划里type是index代表遍历整个索引树扫描行数超过了一百万Extra里显示Using filesort要做额外的排序操作查询执行时间超过了八秒。很多人看到这里会疑惑明明建了create_time的索引为什么性能还是这么差因为OFFSET十万MySQL要先扫过十万条无用的索引记录再取后面的二十条排序和回表的开销非常大。优化之后我们改成了书签分页的写法利用上一页最后一条记录的create_time作为查询条件调整后的EXPLAIN输出是这样的表格id select_type table type possible_keys key key_len ref rows Extra1 SIMPLE orders range idx_ctime idx_ctime 5 NULL 20 Using index优化之后type从index变成了range直接在索引里定位到符合条件的范围扫描行数从一百万降到了二十Using filesort完全消失了直接走覆盖索引拿到数据查询时间从八秒降到了十毫秒以内。从这个对比里你能清晰看到优化的本质不是换了一个索引而是把“遍历一百万行”的逻辑变成了“直接定位二十行”的逻辑执行计划的每一列都发生了质的变化。第三组对比是多表关联优化的场景。优化前的SQL是三张表LEFT JOIN原始的EXPLAIN输出是这样的表格id select_type table type key key_len ref rows Extra1 SIMPLE users ALL NULL 0 NULL 2000000 NULL1 SIMPLE orders ALL NULL 0 NULL 15000000 Using where; Using join buffer (hash join)1 SIMPLE coupons ALL NULL 0 NULL 8000000 Using where; Using join buffer (hash join)这个执行计划里三张表的type都是ALL全表扫描MySQL用了哈希连接的方式把所有数据加载到内存里做关联总扫描行数达到了两千万乘以八百万中间结果集直接爆炸查询执行时间超过了三十秒还把数据库的内存打满导致大量其他查询超时。优化之后我们调整了关联顺序给关联字段都加上了索引调整后的EXPLAIN输出是这样的表格id select_type table type key key_len ref rows Extra1 SIMPLE users range idx_regtime 5 NULL 50000 Using where1 SIMPLE orders ref idx_userid 4 test.users.user_id 32 Using index1 SIMPLE coupons ref idx_userid 4 test.users.user_id 16 Using index优化之后驱动表users的扫描行数从两百万降到了五万后面两张被驱动表的type都变成了ref每次关联只需要扫描几十行总扫描行数从几千万降到了几十万Extra里的哈希连接缓冲完全消失了查询时间从三十秒降到了八百毫秒数据库的内存使用率直接恢复正常。这个案例让我明白多表关联优化的核心就是通过EXPLAIN找到错误的驱动表把大驱动表换成小驱动表让每一步关联的扫描行数都尽可能少。三、EXPLAIN的高级用法90%的人都不知道的技巧除了最基础的EXPLAIN SELECTMySQL还提供了很多高级用法掌握这些技巧你能看到更多优化器的隐藏细节解决很多普通EXPLAIN定位不了的复杂问题。第一个高级技巧是EXPLAIN ANALYZE这是MySQL 8.0之后推出的功能它不仅会生成优化器预估的执行计划还会真正执行SQL输出每一步操作的实际执行时间、实际返回行数、循环次数。很多时候优化器的预估行数和实际行数差距非常大导致优化器选择了错误的执行计划普通的EXPLAIN根本发现不了这个问题而EXPLAIN ANALYZE能直接把预估和实际的差异摆在你面前。我之前遇到过一个案例优化器预估扫描行数是一千行但实际执行扫描了一百万行EXPLAIN ANALYZE一跑出来立刻就发现了统计信息过期的问题执行ANALYZE TABLE更新统计信息之后优化器立刻选择了正确的索引性能直接恢复正常。第二个高级技巧是EXPLAIN FORMATJSON它会以JSON格式输出完整的执行计划里面包含了普通表格格式没有的详细成本信息。你可以看到优化器计算出来的I/O成本、CPU成本、总查询成本通过对比不同索引的成本值你能精准知道优化器为什么选择了A索引而不是B索引。很多时候你觉得明明这个索引更好但优化器偏偏选了另一个用FORMATJSON看一下成本计算立刻就能明白问题出在哪是索引的区分度不够还是统计信息不准根本不用瞎猜。第三个高级技巧是通过EXPLAIN的执行计划反推优化器的决策边界。很多人不知道MySQL优化器选择索引的判断逻辑其实你可以通过构造不同数据量的测试表不断调整数据分布用EXPLAIN观察优化器什么时候从走索引变成走全表扫描你就能精准掌握优化器的选择阈值。我自己测试过当查询需要返回的数据量超过全表的百分之二十左右时优化器通常会放弃索引选择全表扫描因为这时候回表的随机IO开销已经超过了全表扫描的顺序IO开销。掌握这个边界之后你设计索引的时候就能提前预判优化器的选择不会出现建了索引却不被使用的尴尬情况。我做SQL优化这么多年最深的一个感悟是不要靠猜去优化SQL不要靠经验去试索引永远让EXPLAIN的执行计划告诉你真相。很多开发同学优化SQL的时候全靠“蒙”加一个索引试一下不行就再加一个试到性能好为止这样不仅效率低还很容易引入冗余索引拖慢写入性能。而真正的高手拿到一条慢SQL先跑EXPLAIN从type、key、key_len、Extra这几个字段里一眼就能定位到性能瓶颈然后针对性地调整索引或者改写SQL一次就能优化到位。EXPLAIN不是一个面试里的考点它是你日常工作中排查慢查询最锋利的武器把它用熟、用透你会发现之前很多解决不了的性能问题现在看一眼执行计划就有了思路。注意本文所介绍的软件及功能均基于公开信息整理仅供用户参考。在使用任何软件时请务必遵守相关法律法规及软件使用协议。同时本文不涉及任何商业推广或引流行为仅为用户提供一个了解和使用该工具的渠道。你在生活中时遇到了哪些问题你是如何解决的欢迎在评论区分享你的经验和心得希望这篇文章能够满足您的需求如果您有任何修改意见或需要进一步的帮助请随时告诉我感谢各位支持可以关注我的个人主页找到你所需要的宝贝。博文入口山峰哥-CSDN博客复制到【浏览器】打开即可,宝贝入口常用软件宝贝精品文件作者郑重声明本文内容为本人原创文章纯净无利益纠葛如有不妥之处请及时联系修改或删除。诚邀各位读者秉持理性态度交流共筑和谐讨论氛围