别再用直觉写SQL了,学会看执行计划才是调优的第一步‌

发布时间:2026/6/26 9:23:43
别再用直觉写SQL了,学会看执行计划才是调优的第一步‌ 别再用直觉写SQL了学会看执行计划才是调优的第一步‌你有没有遇到过这种情况同样的业务需求同事写的SQL跑得飞快你写的却慢得像蜗牛可你左看右看愣是看不出两条SQL有什么区别。你开始怀疑是不是自己表名写错了是不是数据库针对你甚至开始怀疑自己是不是不适合干这行。别急着自我否定问题很可能出在一个被你长期忽视的地方——执行计划。我见过太多开发同学写SQL全凭直觉觉得JOIN比子查询快觉得建了索引就万事大吉觉得只要功能跑通了代码就合格了。直到某天线上数据量涨上来系统开始频繁超时才手忙脚乱地到处求救。其实MySQL早就把SQL的执行过程明明白白地告诉你了只是你从来没认真看过。那个工具叫EXPLAIN它就像医院里的CT扫描仪能把一条SQL内部怎么运行、走了哪个索引、扫描了多少行数据全部照得一清二楚。今天这篇文章我想带你真正走进执行计划的世界用几个真实的案例告诉你学会看执行计划到底能帮你解决多少棘手的问题。一、执行计划到底是什么为什么你一直没学会先回答一个最基本的问题执行计划是什么简单说你写了一条SQL交给MySQLMySQL不是直接去执行而是先让优化器分析一下决定用什么样的方式来执行最高效。是先查A表再关联B表还是反过来是用索引查还是全表扫描是用嵌套循环还是用哈希连接优化器会综合评估各种因素选出一个它认为最优的方案这个方案就是执行计划。EXPLAIN命令的作用就是把优化器选定的这个方案展示给你看。你只需要在SQL前面加上EXPLAIN关键字就能看到详细的执行过程。那为什么很多人学不会看执行计划呢我觉得有两个原因。第一是输出结果看起来太复杂一堆字段密密麻麻不知道重点看哪里。第二是平时数据量小SQL怎么跑都快没有动力去学。等到数据量大了出问题临时抱佛脚又看不懂干脆直接上网搜解决方案换个写法或者加个索引问题暂时解决了但下次遇到新问题还是不会。其实执行计划没那么难你不需要把所有字段都搞懂只要抓住三个核心指标就能解决百分之八十的问题。这三个指标是type、key和rows。下面我结合一个真实案例带你一步步看懂它们。二、从一次线上事故说起手把手教你读执行计划去年我们团队接手了一个电商项目的优化工作。运营同事反馈后台订单列表页面加载特别慢点进去要等十几秒才能看到数据。我打开慢查询日志一看问题SQL长这样sqlSELECT o.order_id, o.order_sn, o.customer_name, o.order_amount,o.order_status, o.create_time, d.product_name, d.quantityFROM orders oLEFT JOIN order_detail d ON o.order_id d.order_idWHERE o.create_time 2024-01-01AND o.create_time 2025-01-01ORDER BY o.order_id DESCLIMIT 20;这条SQL的业务逻辑很清晰查询2024年全年的订单关联订单明细表按订单ID倒序排列取前20条。看起来人畜无害但执行时间居然要十二秒。我第一反应是create_time没索引但开发同学告诉我这个字段已经建了索引。那问题出在哪我直接上EXPLAINsqlEXPLAIN SELECT o.order_id, o.order_sn, o.customer_name, o.order_amount,o.order_status, o.create_time, d.product_name, d.quantityFROM orders oLEFT JOIN order_detail d ON o.order_id d.order_idWHERE o.create_time 2024-01-01AND o.create_time 2025-01-01ORDER BY o.order_id DESCLIMIT 20;输出结果让我瞬间找到了问题。orders表的type列显示为ALLrows显示为一百八十万。这说明orders表走了全表扫描扫描了整整一百八十万行数据。明明create_time有索引为什么没用上我继续往下看Extra列发现了一行字Using filesort。这说明排序操作也用了文件排序没有走索引。两个问题叠加在一起十二秒的查询时间就不奇怪了。为什么优化器选择了全表扫描我分析了一下orders表里2024年的数据大概有一百五十万条占总数据量的百分之八十以上。优化器评估后认为与其通过索引筛选出百分之八十的数据再回表查询不如直接全表扫描来得快。这个判断在数据量小的时候没问题但一百八十万行全表扫描再加上文件排序性能自然就崩了。问题定位清楚了接下来就是优化。我的思路是既然优化器不愿意用create_time的索引那我就给它一个更好的索引让它没有理由拒绝。我建了一个覆盖索引把查询需要的字段都包含进去sqlALTER TABLE orders ADD INDEX idx_time_order (create_time, order_id, order_sn, customer_name, order_amount, order_status);这个索引的好处是WHERE条件里的create_time能过滤数据ORDER BY里的order_id能避免文件排序SELECT里需要的字段都在索引里不需要回表查询。再次执行EXPLAINtype变成了rangekey显示使用了idx_time_orderrows降到了二十万Extra列里的Using filesort也消失了。实际执行时间从十二秒降到了零点三秒提升了四十倍。运营同事再打开订单列表页面几乎是秒出结果。这个案例告诉我们看执行计划不是简单地扫一眼而是要结合type、key、rows和Extra这几个字段综合分析找到性能瓶颈的根源。下面我把执行计划里最关键的几个字段详细拆解一下帮你建立一套快速诊断的方法。三、执行计划核心字段详解记住这五个就够了执行计划的输出结果有很多列但日常调优中你只需要重点关注下面五个。第一个是id列。它表示查询的执行顺序id越大越先执行id相同则从上到下执行。这个在复杂查询中特别有用比如包含子查询的SQL你可以通过id判断MySQL先执行了哪部分。第二个是type列这是最重要的一个字段。它表示MySQL访问数据的方式从高效到低效依次是system、const、eq_ref、ref、range、index、ALL。我平时看执行计划第一眼就找type列如果看到ALL说明全表扫描了必须想办法优化。如果看到index说明虽然走了索引但是扫描了整个索引树也需要关注。一般要求至少达到range级别最好能达到ref。system和const比较少见只有查询条件能精确定位到一行数据时才会出现比如用主键或唯一索引查单条记录。eq_ref通常出现在联表查询中用主键或唯一索引做关联。ref是最常见的用非唯一索引查询可能返回多行。range是范围查询比如大于、小于、BETWEEN这些。index是扫描整个索引ALL是扫描整个表。第三个是key列。它告诉你MySQL实际使用了哪个索引。如果你明明建了索引但key列显示NULL说明索引没生效需要排查原因。常见的原因包括索引列被函数包裹、发生了隐式类型转换、LIKE以百分号开头、复合索引没遵循最左前缀原则等。第四个是rows列。它显示MySQL预估需要扫描的行数。这个数字是估算值不是精确值但可以作为参考。如果rows很大比如几十万上百万说明查询效率可能有问题。优化之后rows应该有明显下降。第五个是Extra列。它包含一些额外的信息其中几个值需要特别警惕。Using filesort表示MySQL需要额外做排序操作无法用索引完成排序数据量大时很耗性能。Using temporary表示MySQL需要创建临时表来处理查询常见于GROUP BY和DISTINCT操作。Using where表示MySQL在存储引擎返回数据后还需要在Server层做过滤。这些信息能帮你判断查询的瓶颈在哪里。记住这五个字段日常工作中百分之九十的SQL问题都能定位。下面我再用一个案例演示如何综合运用这些字段来诊断问题。四、一个慢查询的完整诊断过程从定位到优化有一次业务方反馈用户画像分析页面加载很慢我查到了这条SQLsqlSELECT u.user_id, u.nickname, COUNT(o.order_id) as order_count,SUM(o.order_amount) as total_amountFROM users uLEFT JOIN orders o ON u.user_id o.user_idWHERE u.register_time 2024-06-01AND u.user_level VIPGROUP BY u.user_idORDER BY total_amount DESCLIMIT 100;先看业务逻辑查询2024年6月之后注册的VIP用户统计他们的订单数量和消费总额按消费总额倒序排列取前100名。我执行EXPLAIN输出结果让我皱起了眉头。users表的type是ALLrows是三十万。orders表的type也是ALLrows是二百万。Extra列里出现了Using temporary和Using filesort。三个问题同时出现两张表都全表扫描用了临时表做分组用了文件排序做排序。这条SQL在测试环境跑了一分钟还没出结果。诊断思路是这样的先解决全表扫描的问题再解决临时表和文件排序的问题。第一步给users表加索引。WHERE条件里有两个字段register_time和user_level。我建了一个联合索引把区分度更高的user_level放在前面sqlALTER TABLE users ADD INDEX idx_level_register (user_level, register_time);第二步给orders表加索引。JOIN的关联字段是user_idGROUP BY也用到了user_id所以给user_id加索引sqlALTER TABLE orders ADD INDEX idx_user_id (user_id);第三步考虑覆盖索引。SELECT里需要user_id、nickname、order_id、order_amount这几个字段如果能让查询在索引里完成不需要回表性能会更好。但考虑到users表的字段较多全部放进索引会让索引变得很大我选择只覆盖关键字段剩下的通过回表获取。优化后再次执行EXPLAINusers表的type变成了refkey使用了idx_level_registerrows降到了八千。orders表的type变成了refkey使用了idx_user_idrows降到了五十万。Extra列里的Using temporary和Using filesort还在但整体扫描行数大幅下降查询时间从一分钟降到了两秒。如果还想进一步优化可以考虑把total_amount的排序也放到索引里但那样索引会变得很复杂维护成本也高。两秒的查询时间对于后台分析页面来说已经可以接受我就没有再继续优化。这个案例完整展示了从定位问题到解决问题的全过程先看type发现全表扫描再看key确认索引没生效然后看rows评估扫描行数最后看Extra了解额外的性能消耗。四个步骤走下来问题定位和优化方向就非常清晰了。五、执行计划对比同一个需求不同写法的性能差异执行计划还有一个很实用的功能就是对比不同SQL写法的执行效率。很多时候同一个业务需求可以用多种SQL实现但性能差异可能非常大。通过对比执行计划你能直观地看到哪种写法更优。我举一个经典的例子查询每个用户的最新一条订单记录。这个需求有三种常见写法。第一种写法用子查询sqlSELECT * FROM orders o1WHERE o1.order_id (SELECT MAX(o2.order_id)FROM orders o2WHERE o2.user_id o1.user_id);EXPLAIN的结果显示外层orders表type为ALL全表扫描。内层子查询type为ref走了user_id索引。整体来看外层全表扫描是瓶颈每个用户都要执行一次子查询效率很低。第二种写法用JOINsqlSELECT o1.* FROM orders o1INNER JOIN (SELECT user_id, MAX(order_id) as max_idFROM ordersGROUP BY user_id) o2 ON o1.user_id o2.user_id AND o1.order_id o2.max_id;EXPLAIN的结果显示子查询部分type为index扫描了整个user_id索引Extra显示Using index说明用了覆盖索引。外层JOIN的type为eq_ref用主键关联效率很高。整体比第一种写法快很多。第三种写法用窗口函数MySQL 8.0以上支持sqlSELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_id DESC) as rnFROM orders) t WHERE t.rn 1;EXPLAIN的结果显示子查询部分type为ALL全表扫描Extra显示Using filesort因为窗口函数需要排序。这种写法虽然代码简洁但性能不如第二种JOIN写法。通过执行计划对比结论很明确第二种JOIN写法最优。如果没有执行计划你可能会凭直觉选择窗口函数因为代码看起来更现代更简洁但实际性能可能差好几倍。六、养成看执行计划的习惯比掌握任何优化技巧都重要写了这么多案例我想表达的核心观点其实很简单执行计划是SQL调优的起点也是最重要的工具。你不需要把MySQL的优化器原理研究得多透彻也不需要记住所有优化法则只要每次写SQL的时候习惯性地用EXPLAIN看一眼就能避免大部分性能问题。我给自己定的规矩是任何涉及两张以上表的查询或者单表数据量超过十万行的查询提交代码前必须在测试环境跑一遍EXPLAIN把结果截图附在代码评审里。这个习惯坚持了三年线上SQL性能问题减少了百分之九十以上。如果你现在还没有这个习惯我建议从今天开始培养。不需要一下子把所有字段都搞懂先学会看type、key、rows这三个遇到问题再查Extra。慢慢地你会发现自己的SQL水平在不知不觉中提升了一个档次。最后说一句执行计划不是万能的它只能告诉你MySQL打算怎么执行不能告诉你为什么这么执行。如果遇到执行计划明显不合理的情况可能需要进一步分析表统计信息、优化器参数等。但那是进阶话题了对于大多数开发同学来说能把执行计划看懂、用好已经足够应对日常工作中百分之九十的SQL性能问题了。注意本文所介绍的软件及功能均基于公开信息整理仅供用户参考。在使用任何软件时请务必遵守相关法律法规及软件使用协议。同时本文不涉及任何商业推广或引流行为仅为用户提供一个了解和使用该工具的渠道。你在生活中时遇到了哪些问题你是如何解决的欢迎在评论区分享你的经验和心得希望这篇文章能够满足您的需求如果您有任何修改意见或需要进一步的帮助请随时告诉我感谢各位支持可以关注我的个人主页找到你所需要的宝贝。博文入口山峰哥-CSDN博客复制到【浏览器】打开即可,宝贝入口常用软件宝贝精品文件作者郑重声明本文内容为本人原创文章纯净无利益纠葛如有不妥之处请及时联系修改或删除。诚邀各位读者秉持理性态度交流共筑和谐讨论氛围