想象一下,你开了一家生意火爆的餐馆,后厨有一位“总调度”(数据库优化器)。每次有客人点菜(SQL 查询),这位总调度不会直接闷头做饭,而是先写一张“后厨作战计划”(执行计划),上面写着:先洗菜还是先切肉、用哪个炉灶、预估要多少分钟。你作为老板,只要看一眼这张计划单,就能知道这单菜会不会卡在某个环节。 但问题是——这张计划单是给“内行”看的,上面全是缩写和数字。如果看不懂,你就只能干等,直到客人在催菜(慢查询告警)。这时你就需要一张“白话翻译表”:EXPLAIN 就是数据库递给你的那张“后厨作战计划”,而看懂它,就是你作为“老板”管理后厨、优化出餐效率的第一步。
EXPLAIN 是所有 SQL 优化工程师的基础功。学会读懂它,你就能在慢查询面前从“盲猜”变成“对症下药”,在软件工程师、数据分析师的岗位上直接提升排查效率。
可视化演示
展示SQL查询从解析到执行的完整流程,通过EXPLAIN输出表解读type/rows/Extra等关键字段,帮助理解数据库查询优化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
EXPLAIN执行计划解析 SQL→解析→优化→执行四步流程,EXPLAIN展示每一步的决策 type=ALL全表扫描需优化,type=range走索引高效 排查慢SQL的必备工具,看懂执行计划才能精准优化
认识执行计划
回到那家餐馆。你作为老板,想知道今晚后厨忙不忙、哪道菜会卡住,最直接的办法就是看一眼总调度写的“作战计划单”。数据库里的这张计划单,就叫执行计划(Execution Plan)。它是优化器(Optimizer)根据 SQL 语句、表结构和统计信息,花了几毫秒“脑补”出来的最佳执行路线。而 EXPLAIN 就是打开这张计划单的钥匙——只需在 SQL 前面加上 EXPLAIN 关键字,数据库就会把计划展示给你,而不会真的去执行这条 SQL。
执行计划里包含多个字段,就像计划单上写着“哪个厨师做、用哪个炉子、切几斤肉”。核心字段包括:id(步骤编号)、select_type(查询类型)、table(操作哪张表)、type(访问类型)、possible_keys(候选索引)、key(实际选用的索引)、rows(预估扫描行数)、filtered(过滤比例)和 Extra(额外信息)。比如 rows 显示 100000 而实际查询只需要 1 条,说明这条路线图明显绕了远路。
打个比方,你拿到一份计划单,发现 type 是 ALL、rows 是 100 万,就知道这位厨师打算把整个仓库翻个底朝天。此时你就该考虑:是不是该给这张表加个索引?执行计划的意义正在于此——让优化器的决策过程变得透明,你才能有的放矢地调优。
- id这是步骤的编号。id 相同的步骤属于同一组查询,会从上到下依次执行;id 越大越先执行,就像后厨“先焯水、再爆炒”的先后顺序。
- select_type表示查询的类型。
SIMPLE是普通查询,SUBQUERY是子查询,UNION是联合查询。复杂 SQL 会拆成多步。 - table这一步操作的是哪张表。对应后厨里的“食材柜”编号。
- type访问类型,即“怎么取数据”的方式。从最好到最差依次是:system → const → eq_ref → ref → range → index → ALL。
- rows 与 Extrarows 是预估要读多少行,Extra 是附加说明,如是否用了临时表、是否需要额外排序。
访问类型type
访问类型 type 是执行计划里最重要的性能指示器,它直接告诉你数据库打算用多“粗暴”的方式获取数据。想象你去商场找一家店:最优雅的方式是打开手机导航直接到店门口(const);次一点是知道它在这层楼,顺着指示牌找过去(ref);再差一点是整层楼一间间看招牌(range);最离谱的是把整个商场从负一楼到顶楼全部逛一遍(ALL)。SQL 的世界里,这种“找法”就是 type。
具体来说:const 表示用主键或唯一索引等值查询,最多返回一行,是最高效的方式;eq_ref 出现在联表查询中,被驱动表用主键或唯一索引匹配;ref 表示使用普通索引等值匹配,可能返回多行;range 表示索引范围扫描,如 BETWEEN、> < 等;index 表示全索引扫描,比全表稍好但也好不到哪去;ALL 则是全表扫描,没有任何索引可用,相当于把整张表从头到尾读一遍。对优化器而言,ALL 是最后的无奈之选。
你可以这样记忆:type 是从“精准打击”到“地毯式搜索”的降级谱系。日常调优时,目标就是让慢查询的 type 从 ALL 提升到至少 range 或 ref。有一次线上慢查询,就是因为在 WHERE 条件字段上没有索引,type 显示 ALL、rows 达到 800 万,加了普通索引后 type 跳到了 ref,查询从 3 秒降到 20 毫秒——这就是 type 的威力。
- system 与 constsystem 是表只有一行(系统表)的特例,const 是基于主键或唯一索引的等值查询,最多返回一行。相当于导航精确到门牌号。
- eq_ref联表时被驱动表用主键匹配,每次驱动表一行,被驱动表最多返回一行。典型例子是两表按主键关联。
- ref使用非唯一索引的等值查询,可能返回多行。相当于知道店铺在这一层,但该层有多家同名店。
- range索引范围扫描,如
> < BETWEEN IN。相当于在索引的“目录页”里翻页查找。 - index 与 ALLindex 是遍历整棵索引树(比全表小),ALL 是遍历整张表的每一行数据。两者都是“暴力找法”,只是 ALL 更暴力。
索引利用
知道了 type 的威力,自然要问:怎么让 type 变好?答案藏在索引里。EXPLAIN 中有两个专门描述索引的字段:possible_keys 和 key。继续用商场找店的比喻:possible_keys 是“这一层可能挂着你目标店铺招牌的所有楼层指引图”,key 是你最终照着走的那一张。很多时候候选索引有好几个,优化器会根据统计信息挑一个它认为最划算的。
key_len 则告诉你实际使用的索引字节长度,它能帮你判断是否用上了联合索引的全部字段。比如联合索引 (a, b, c),如果 key_len 只等于 a 字段的长度,说明 MySQL 只用了前缀字段 a 来定位,b 和 c 因为中间跳跃或范围条件而没被用上。此外还有一个高价值概念叫覆盖索引(Covering Index):当查询需要的所有列都恰好包含在索引中时,数据库可以直接从索引树取数据,连回表查原数据都省了。
这里还有一个非常重要的衍生概念——最左前缀原则(Leftmost Prefix Rule)。联合索引 (a, b, c) 相当于按 a、a+b、a+b+c 建立了三棵索引树,查询时如果条件里没有 a,整个索引就用不上。这就是为什么很多 SQL 明明有索引,type 却是 ALL——因为你跳过了联合索引的“最左列”。在优化时,我们应该用 key_len 判断联合索引到底吃满了几个字段,再用覆盖索引的思路减少回表次数。
- possible_keys优化器认为可能用到的索引列表。注意“可能”二字——它只是候选池,不代表一定被选中。
- key实际选用的索引名。如果为 NULL,说明候选池落空,相当于没走索引。
- key_len实际使用的索引字节长度。通过它推算哪些索引列被真正使用。例如
varchar(10)的字符集是 utf8mb4,一个字符占 4 字节,那么key_len可能显示 40 + 变长标记。 - 覆盖索引查询的所有列都包含在索引中。Extra 字段出现
Using index表示“直接在索引里找齐了数据,不用回表”。
扫描行数rows
rows 字段是执行计划里最直观的“距离感”来源。它表示优化器预估需要读取多少行才能拿到结果——注意是“预估”,不是实际值。这就像总调度看了一眼前厅的人数,估算“这桌大概要 20 分钟”,但真正做起来可能快也可能慢。rows 越小,说明这条路线越短;rows 巨大而查询结果只有几条,说明优化器选了一条绕远的路。
与 rows 搭配的还有 filtered 字段,表示从 rows 读取的行中,满足条件并最终返回的比例。比如 rows 显示 10000,filtered 是 1%,意味着预计只有 100 行是有效的。如果 filtered 很小而 rows 很大,说明这一步扫了大量无关数据,这个“过滤点”很可能就是性能瓶颈所在。
用等式理解:rows × filtered% 才是“有效阅读量”。理想状态是 rows 接近实际结果集大小。我在优化一个分页查询时发现 rows 显示 50 万,filtered 只有 0.1%,这意味着存储引擎把 50 万行都捞上来,最终只留 500 行——因为 WHERE 里的条件字段没有索引,这就是典型的“搬运工累死,厨师闲死”。
- rows 预估优化器基于统计信息估算的“要读取的行数”。它不是真实值,只是成本估算的原料。
- filtered 过滤比例表示从预估行数中经过 WHERE 条件过滤后,剩余有效行数的百分比。
- 权衡关系
rows大且filtered小,说明存储引擎层传了大量无用数据到服务层,需要关注索引是否合理;rows小且filtered高,说明过滤能力弱,可能缺少合适的索引。
解读Extra
Extra 字段就像执行计划单最下方的“备注栏”,里面藏着优化器对你的“悄悄话”。最常见的几个“暗语”包括:Using index(正在用覆盖索引,效率好)、Using where(在存储引擎层拿到数据后又做了条件过滤,正常)、Using temporary(用了临时表,通常不好)、Using filesort(无法用索引排序,额外做了一次文件排序,通常不好)、Using join buffer(联表时用缓冲区,代表关联字段无索引)。后厨的比喻在这里非常形象:Using temporary 好比后厨案板不够用,临时在走道支了张折叠桌;Using filesort 好比菜都切好了,却发现没有统一的摆盘顺序,只能再花时间把盘子重新排一遍。
Using filesort 是最常见的性能杀手之一。它不一定真的在磁盘文件中排序,但意味着排序没有走索引,需要额外的 CPU 和内存。如果 ORDER BY 的字段恰好是联合索引的组成部分,排序就能“白嫖”索引的有序性,Extra 就不会出现 filesort。而 Using temporary 通常出现在 GROUP BY 或 DISTINCT 操作中,意味着要建临时表存储中间结果,分组字段没索引时尤其频繁。
我在真实项目中排查过一条慢 SQL:SELECT * FROM orders ORDER BY created_at LIMIT 10。EXPLAIN 显示 Extra 为 Using filesort,rows 高达 200 万。给 created_at 建索引后,Extra 变成了空,排序直接走索引完成,查询从 1.2 秒降到 30 毫秒。Extra 的每个词背后,都是一条清晰的优化指令。
- Using index覆盖索引生效,所有需要的数据都在索引树里拿到了,不需要回表。这是最理想的附注。
- Using where从存储引擎读取行后,在服务层又做了一次条件过滤。如果行数大,说明索引下推没做够。
- Using temporary执行过程中创建了临时表,常见于
GROUP BY、DISTINCT或子查询。 - Using filesort排序无法利用索引完成,额外执行一次排序操作。优化方向是让
ORDER BY走索引。
分析器与实测
前面说的都是优化器的“预估”,但预估终究是估算。如果想知道真实的执行情况,MySQL 8.0 提供了 EXPLAIN ANALYZE 这把“监控镜头”。它在执行 SQL 的同时记录每一步的实际耗时和行数。请注意:EXPLAIN(不执行 SQL)和 EXPLAIN ANALYZE(会真正执行 SQL)有本质区别——后者适合 SELECT 查询,不适合 INSERT/UPDATE/DELETE,因为它真的会跑一遍。
EXPLAIN ANALYZE 的输出以树形格式展示,每个节点都标注了“实际执行时间”和“实际读取行数”。这相当于老板不仅看了作战计划,还站在后厨里掐着表看每道菜的实际出餐时间。比如某一步显示 actual time=12.345..40.678,说明这一步耗时约 40 毫秒。配合 FORMAT=TREE 选项,你可以看到完整的信息树,每一步的真实开销一目了然。
注意一个限制:EXPLAIN ANALYZE 会产生实际影响,所以不要在生产环境随意对大数据表执行。但它的价值在于——预估和实际之间的鸿沟。优化器估算的 rows 可能因为统计信息过期而失真,而 ANALYZE 的真实行数直接暴露问题。有一次我遇到一个预估 1000 行、实际扫了 100 万行的案例,正是靠 EXPLAIN ANALYZE 发现统计信息未更新,执行 ANALYZE TABLE 后优化器路线立刻重回正轨。
- EXPLAIN ANALYZE等同于 “EXPLAIN + 实际执行”。输出每个节点的实际时间(包含起止)与实际行数。
- actual time 语法
actual time=start..end,start 是获取第一批行的时间,end 是处理所有行的时间,单位毫秒。 - FORMAT=TREE以树形结构输出,父子节点关系清晰表达嵌套循环、子查询等步骤。
慢查询排查
把前面的技能串联起来,就是一套完整的慢查询排查方法论。这就好比老板总结出的“后厨巡检流程”:先找出哪桌客人在催菜(慢查询日志),再看这张菜的作战计划单(EXPLAIN),最后针对问题环节调整(加索引或改 SQL)。实战中,我建议你按照固定的顺序执行:先看 type,再看 rows,然后看 Extra,最后用 EXPLAIN ANALYZE 验证真实耗时。
一个标准的排查模板是这样的:第一步,从慢查询日志(slow query log)中找到耗时最长的 SQL;第二步,给这条 SQL 加上 EXPLAIN,观察 type 是否出现 ALL、rows 是否巨大;第三步,若 Extra 出现 Using temporary 或 Using filesort,先考虑加合适的索引;第四步,用 EXPLAIN ANALYZE 看每一步真实耗时,确认瓶颈是否消除。这套流程就像医生看病:先问诊(找慢 SQL)、再拍片(EXPLAIN)、最后开药(加索引)。
一个真实的例子:曾经优化过一条报表 SQL,三个表联查,总耗时 11 秒。先用 EXPLAIN 看到驱动表 type 是 ALL、rows 400 万,Extra 里还有 Using temporary。优化方案是:给被驱动表的关联字段加索引,把 GROUP BY 字段包含进联合索引。改动后 type 变 ref、Extra 只剩 Using where,耗时降到 180 毫秒。记住一个原则:90% 的慢查询,都是因为索引没用上。
- 定位慢 SQL先通过 MySQL 的慢查询日志或性能监控工具找到“耗时最长的那桌客人”。没有这一步,一切都是空谈。
- EXPLAIN 初步诊断给慢 SQL 加上 EXPLAIN,重点看 type 是否较差、rows 是否离谱、Extra 是否有临时表或文件排序。
- 索引对症下药根据诊断结果建索引。type 是 ALL → 加 WHERE 条件索引;Extra 是 filesort → 加排序列索引;覆盖索引需求 → 把查询列并入联合索引。
AI 应用场景
场景一:AI 数据管道中的 SQL 性能诊断
阶段:数据准备(Data Preparation)
解决的问题:训练 AI 模型前需要从业务库抽取海量特征数据。传统方式是直接跑 SQL 抽取,但遇到慢查询只能干等,抽数任务超时导致整个训练流程中断。用 EXPLAIN 执行计划,可以提前发现抽取 SQL 的访问类型是否为 ALL、扫描行数是否过大,在源头优化 SQL 或建立合适索引,让数据管道不再卡壳。
具体案例:某推荐系统的特征抽取任务,每天要从订单表(2 亿行)中取近 30 天数据。原先 SQL 执行耗时 42 分钟,EXPLAIN 显示 type=ALL、rows=2 亿。通过给 order_date 和 user_id 建立联合索引后,type 变为 range,耗时降到 90 秒,AI 模型训练数据每天准时送达。
场景二:大模型应用的提示词日志分析
阶段:模型运维与日志分析(LLMOps)
解决的问题:大模型应用上线后,需要分析用户提示词日志和推理会话数据。这些日志表动辄数千万行,统计“每日活跃用户”或“平均请求延迟”的 SQL 经常慢如蜗牛。用 EXPLAIN 定位后,通过索引优化让统计查询在毫秒级完成,LLMOps 团队才能实时监控模型调用情况。
具体案例:某 AI 客服应用每天产生 500 万条会话日志。一条按 user_id 统计会话数的 SQL 原本需要 15 秒(type=ALL),用 EXPLAIN 看到问题后为 user_id + created_at 建了联合索引,统计耗时降到 30 毫秒,运营大屏得以实时刷新。
Python 演示
import pymysql def analyze_query(sql, conn): """执行 EXPLAIN 并打印关键诊断信息.""" explain_sql = "EXPLAIN " + sql with conn.cursor() as cur: cur.execute(explain_sql) plans = cur.fetchall() return plans def diagnose(plans): """根据执行计划返回诊断结论.""" issues = [] for row in plans: type_name = row[4] # type 字段 rows = row[9] # rows 字段 extra = row[11] # Extra 字段 key = row[6] # key 字段 table = row[2] # table 字段 if type_name == "ALL": issues.append(f"表 {table} 全表扫描,建议检查 WHERE 条件索引") if rows and rows > 10000: issues.append(f"表 {table} 预估扫描 {rows} 行,注意控制查询范围") if extra and "Using temporary" in extra: issues.append(f"表 {table} 使用了临时表,GROUP BY/DISTINCT 字段需建索引") if extra and "Using filesort" in extra: issues.append(f"表 {table} 使用了文件排序,ORDER BY 字段需建索引") if key is None: issues.append(f"表 {table} 未命中任何索引") return issues def main(): conn = pymysql.connect( host="127.0.0.1", user="root", password="secret", database="shop", charset="utf8mb4", ) slow_sql = """ SELECT user_id, COUNT(*) FROM orders WHERE created_at >= '2024-01-01' GROUP BY user_id ORDER BY COUNT(*) DESC LIMIT 10 """ plans = analyze_query(slow_sql, conn) print("执行计划:") for p in plans: print(" type=%s, key=%s, rows=%s, Extra=%s" % (p[4], p[6], p[9], p[11])) print("\n诊断结论:") for issue in diagnose(plans): print(" [风险] " + issue) conn.close() if __name__ == "__main__": main()
explain_sql = "EXPLAIN " + sql在原始 SQL 前拼接 EXPLAIN,让 MySQL 返回执行计划而不是真的执行。plans = cur.fetchall()执行计划是表格形式的返回结果,每一行代表一条执行步骤,用元组列表接收。type_name = row[4]按列索引取值,第 5 列(索引 4)就是访问类型 type。rows = row[9]第 10 列(索引 9)是预估扫描行数。extra = row[11]第 12 列(索引 11)是 Extra 字段,包含临时表、文件排序等线索。if type_name == "ALL"全表扫描是最危险的信号,直接提示检查索引。conn = pymysql.connect(...)建立数据库连接,注意参数按实际环境修改。
常见误区
误区“我在生产库上跑 EXPLAIN 会把大表数据都查一遍,把数据库拖垮。”很多初学者以为 EXPLAIN 等于执行一遍 SQL,因此在生产环境畏手畏脚。
正解标准的 EXPLAIN SELECT ... 只生成执行计划,不会真正执行 SQL,对数据无任何影响。但要注意 EXPLAIN ANALYZE 是例外——它真的会执行 SQL。就像“看导航”不会让你真的到目的地,只有“跟着导航开车”才会。所以排查生产问题时,用普通 EXPLAIN 绝对安全,想实测耗时再用 EXPLAIN ANALYZE 并挑低峰期。
为何常见:EXPLAIN 和 EXPLAIN ANALYZE 名字太像,而且很多线上博客混用这两个概念,导致初学者把两者画上等号。
误区“EXPLAIN 显示 rows=100000,说明这条 SQL 真的读了 10 万行,我要按这个数去优化。”很多人把 rows 当作真实统计数据。
正解rows 是优化器基于统计信息和索引基数算出的“预估扫描行数”,不是实际值。统计信息过期后,rows 可能严重失真。真正的实际行数要用 EXPLAIN ANALYZE 看。就像导航说“预计 30 分钟到”,但实际路况变化,可能 15 分钟就到了。优化时 rows 用来做趋势判断——从 100 万降到 1000 说明优化有效。
为何常见:EXPLAIN 的输出看起来像是“执行后的结果”,加上字段名叫 rows 而非 estimated_rows,非常容易误读。
误区“我明明建了索引,为什么 EXPLAIN 还显示 ALL?MySQL 是不是有问题?”初学者总以为索引是万能药。
正解加索引 ≠ 一定走索引。如果查询条件是 LIKE '%关键词%'、对索引列做了函数运算(如 DATE(created_at) = '2024-01-01')、或违反了最左前缀原则,索引都会失效。另外,如果优化器判断表中 80% 的行都满足条件,它会觉得“直接全表扫更快”,干脆弃用索引。这就像你明明有直达电梯,但目的地就在一楼,走过去反而更快——优化器也在算这笔账。
为何常见:对索引的底层机制(B+ 树、最左前缀、成本估算)了解不足,把索引当成简单的“加速开关”。
误区“EXPLAIN 的 Extra 字段是空的,说明这条 SQL 很完美,不用优化。”这种想法很危险。
正解Extra 为空通常只表示“没有额外的排序、临时表、覆盖索引等标记行为”,并不代表 SQL 高效。type=ALL、rows=几千万但 Extra 为空的情况非常常见——全表扫描本身就是最大的问题,只是没有额外的“附注”而已。就像厨师炒菜很快,但他把整个仓库的食材都翻了一遍才找到那根葱,Extra 一样是空的。看执行计划永远先看 type 和 rows,再看 Extra。
为何常见:人的注意力容易被“有标志”的东西吸引,Extra 有内容时会盯着看,一旦为空就松懈了。
误区“我上午看 EXPLAIN 是 ref,下午怎么变成 ALL 了?肯定有人动了我的表结构!”有人对执行计划的变化感到困惑。
正解执行计划是优化器按当前统计信息动态算出来的。表数据量变化、统计信息自动更新、索引基数变化、查询条件参数不同,都会导致执行计划改变。相同的 SQL 用不同参数跑,type 可能完全不同——比如 WHERE id > 100 是 range,WHERE id > 1000000 可能是 ALL。这就像早晚高峰走同一条路,导航规划的路线不同。所以执行计划是“当时的快照”,不是“永久的判决书”。
为何常见:把执行计划误解为“编译结果”而非“动态决策”,忽略了优化器的成本模型基于实时统计信息。
面试题
Q1. EXPLAIN 中表示“实际选用的索引”的字段是?
- A.possible_keys
- B.key← 正确答案
- C.rows
- D.type
答案:B。possible_keys 是“候选名单”,key 才是优化器“最终选中”的索引。就像后厨作战计划上,possible_keys 列出所有会用到的食材柜,key 标注实际打开的那个柜子。rows 是预估行数,type 是访问方式,都不是索引字段。
Q2. 以下哪个访问类型 type 性能最差?
- A.const
- B.ref
- C.range
- D.ALL← 正确答案
答案:D。性能排序为 const > eq_ref > ref > range > index > ALL。ALL 是全表扫描,相当于把整个仓库翻一遍。看到 ALL 就要警惕:这是慢查询的头号信号,通常意味着 WHERE 条件没有命中索引。
Q3. 一条 SQL 的 EXPLAIN 结果显示 type=ALL、rows=5000000、Extra 为 NULL。以下哪个结论是正确的?
- A.SQL 没有任何优化空间
- B.Extra 为 NULL 说明没有额外开销
- C.存在全表扫描,应检查 WHERE 条件是否走索引← 正确答案
- D.rows 是实际扫描行数
答案:C。type=ALL 是全表扫描,即使 Extra 为 NULL 也是极大的性能隐患——Extra 为空不代表没问题,它只是没有额外标记。rows 是预估而非实际值。全表扫描 500 万行的第一反应永远是:加索引或改查询条件。
Q4. 关于 EXPLAIN 与 EXPLAIN ANALYZE,下列说法正确的是?
- A.两者都会真正执行 SQL
- B.两者都不会真正执行 SQL
- C.EXPLAIN 不执行 SQL,EXPLAIN ANALYZE 会执行← 正确答案
- D.EXPLAIN 用于 UPDATE,EXPLAIN ANALYZE 用于 SELECT
答案:C。标准 EXPLAIN 只生成执行计划不执行;EXPLAIN ANALYZE 会实际执行并返回真实耗时和行数。EXPLAIN ANALYZE 只适合 SELECT,不适合写操作。生产环境实测务必选低峰期,避免对大表造成压力。
Q5. 联合索引为 (a, b, c),以下哪个查询条件能用上该索引的完整三个字段?
- A.WHERE b = 1 AND c = 2
- B.WHERE a = 1 AND c = 2
- C.WHERE a IN (1,2) AND b = 1 AND c = 2← 正确答案
- D.WHERE a = 1 AND b > 10 AND c = 2
答案:C。A 缺少最左列 a,索引完全失效;B 只有 a 用上,c 因中间跳跃用不上;D 中 b 用了范围条件,导致 c 无法精确定位(连接上的“断链”)。C 中 a 用 IN 但等价于等值匹配组,b 和 c 都能完整命中。联合索引就像拼积木,必须从左边第一块开始拼。
Q6. EXPLAIN 的 Extra 字段出现哪些值说明 SQL 可能需要优化?
- A.Using index
- B.Using filesort← 正确答案
- C.Using temporary← 正确答案
- D.Using where
答案:BC。Using filesort 表示排序无法利用索引,Using temporary 表示用了临时表,两者都是性能杀手。Using index 是覆盖索引的好信号,Using where 是常见的正常过滤。遇到 B、C 时,应通过给 ORDER BY / GROUP BY 字段加索引来消除。
Q7. 一条查询 EXPLAIN 显示 key_len=40,联合索引为 (name varchar(10), age int),字符集 utf8mb4。以下推断最合理的是?
- A.name 和 age 都用上了
- B.只用上了 name 字段← 正确答案
- C.只用上了 age 字段
- D.没有任何字段被使用
答案:B。utf8mb4 下一个字符占 4 字节,varchar(10) 最多占 40 字节,加上变长标记约 41-42 字节。key_len=40 说明只用到 name 字段,age(int 占 4 字节)没用上。如果两个字段都用上,key_len 应约为 45。这就是用 key_len 反推联合索引“吃了几口”的方法。
Q8. 关于 rows 和 filtered 字段的描述,正确的有?
- A.rows 是优化器预估行数,非实际值← 正确答案
- B.filtered 越大,说明有效返回比例越高← 正确答案
- C.rows 很小且 filtered 很小,表明显过滤效果好
- D.rows × filtered% 接近有效返回行数← 正确答案
答案:ABD。rows 是预估行数,A 正确。filtered 是满足 WHERE 条件的比例,越大说明有效行占比高,B 正确。rows 大且 filtered 小才说明扫了大量无用数据,rows 小且 filtered 小反而是“扫得少、过滤后更少”,C 错误。有效返回数 ≈ rows × filtered%,D 正确。
Q9. 某 UPDATE 语句执行很慢,能否用 EXPLAIN ANALYZE 定位性能瓶颈?
- A.可以,EXPLAIN ANALYZE 支持所有 SQL
- B.可以,但需要用 FORMAT=TREE
- C.不可以,EXPLAIN ANALYZE 不适用于写操作← 正确答案
- D.不可以,但可以用普通 EXPLAIN 查看执行计划
答案:C。EXPLAIN ANALYZE 目前主要支持 SELECT,对 UPDATE/DELETE 等写操作不适用。但普通 EXPLAIN 本质是 "EXPLAIN DML",会返回该写操作的执行计划——MySQL 允许对 UPDATE/DELETE/INSERT 执行 EXPLAIN 而不真正修改数据。所以调优慢 UPDATE 时,用普通 EXPLAIN 看执行计划即可。
Q10. 关于覆盖索引,以下说法正确的有?
- A.覆盖索引能让 Extra 出现 Using index← 正确答案
- B.覆盖索引可以避免回表← 正确答案
- C.覆盖索引的列必须包含查询的所有列← 正确答案
- D.覆盖索引只适用于 InnoDB 的辅助索引
答案:ABC。覆盖索引指查询需要的所有列都出现在索引中,此时索引本身就是“数据完整包”,Extra 显示 Using index 且无需回表,A、B、C 都对。D 不对——主键索引本身也能实现覆盖效果。覆盖索引是优化高频查询的利器,但要注意别盲目把太多列塞进索引,会增大索引体积。