想象你是一家高档餐厅的主厨,客人点了一道“红烧狮子头”。你的二厨需要完成备料、调味、炖煮、摆盘好几个步骤——而你最关心的,不是这道菜最终好不好吃,而是备餐过程中哪个环节最耗时、哪一步会卡住。于是你让二厨写一张“备餐计划表”,上面标明每一步要用多少食材、从哪个抽屉拿料、要不要临时搭灶台。这张计划表,就是数据库世界的 EXPLAIN。 MySQL 拿到一条查询语句时,并不会直接“闭眼执行”,优化器会先规划一套执行方案——就像主厨先写好备餐计划。EXPLAIN 则是把这份计划摊开给你看:每一步做什么(id 与 select_type)、用什么方式找数据(type)、用哪把钥匙开锁(key)、预计翻多少箱食材(rows)、有没有隐藏的额外操作(Extra)。读懂这张表,你就能在查询真正“上桌”之前,预判它会不会慢,找到卡脖子的环节。
对后端开发和 DBA 而言,EXPLAIN 是排查 SQL 性能问题的第一把扳手。学会解读执行计划,等于掌握了数据库调优的入口,能在一堆慢查询中快速定位“全表扫描、临时表、文件排序”等隐患,让线上性能问题从“玄学”变成“科学”。
可视化演示
展示EXPLAIN执行计划解读三大核心:执行计划表结构(id/type/key/rows/Extra字段+全表扫描高亮)、type访问类型光谱(system→const→eq_ref→ref→range→index→ALL从优到差)、Extra优化信号(Using index覆盖索引/Using temporary临时表/Using filesort文件排序等关键信号识别)。
本节配有交互动效(进入视口后自动加载,约 3KB)。
执行计划表结构 EXPLAIN输出执行计划表,每行代表一个查询步骤 关键字段:id(执行顺序)、type(访问类型)、key(使用索引)、rows(扫描行数)、Extra(额外信息) type=ALL且key=NULL表示全表扫描,需重点优化 EXPLAIN → 分析type/key/rows/Extra → 定位瓶颈 EXPLAIN是SQL性能优化的第一工具。
EXPLAIN入门
餐厅后厨里,新来的二厨第一次备菜时手忙脚乱:不知道调料放在哪、不知道鱼要腌多久、甚至险些把蒸笼当炒锅。老主厨看不下去,掏出一张备餐卡——上面清清楚楚写着每一步做什么、用什么工具、预计耗时。EXPLAIN 就是数据库的“备餐卡”:MySQL 优化器在执行 SQL 之前,会先规划一条执行路径,EXPLAIN 把这条路径的关键指标摊开给你看。
在 MySQL 里跑一条 EXPLAIN SELECT ...,它不会真的执行查询,而是返回一行或多行表格数据。每一行代表查询中的一个“步骤”——可能是主查询、子查询,也可能是联合查询的某个分支。这些行里的每一列,都像备餐卡上的一个栏目:步骤编号、任务类型、找数据的方式、用的索引、预估行数、额外备注。
这张表的核心价值在于“预判”。备餐卡能在开火之前告诉你哪道菜会翻车,EXPLAIN 能在查询压垮数据库之前告诉你哪条 SQL 会慢。常见的性能隐患——全表扫描、临时表、文件排序——都会在执行计划里露出马脚。学会读它,你就从“等查询卡死再救火”变成了“提前看计划避坑”。
了解了这张备餐卡上有什么栏目后,下一步就是逐个栏目读懂它。先从最重要的“步骤编号”开始——它决定了每件事的先后顺序。
- EXPLAIN 关键字告诉 MySQL 只做计划分析,不真正执行查询,就像主厨只写备餐卡而不开火。
- 查询语句等待被分析的 SQL,通常是 SELECT,也可以是 INSERT 或 UPDATE 等语句。
id执行顺序
备餐卡上每一行最前面的数字,就是步骤编号。主厨写卡有讲究:如果两件事互不依赖,就写同一个编号;如果一件事要等另一件事完成,就另起一个更大的编号。数据库执行计划里的 id 列,正是这个步骤编号,它揭示了查询中各步操作的执行先后。
先看最简单的情况:id 相同。这样的行表示这些步骤是“同一波操作”,MySQL 会按照从上到下的顺序依次执行。就像备餐卡上“洗菜、切菜、装盘”写在同一组编号下,按顺序做就行。再看 id 递增的情况:编号越大,越先执行。这对应“先备好饺子皮,再包饺子”——为外层查询准备数据的内层子查询,总是先跑。最后是 id 为 NULL 的特殊行,它不代表真实步骤,而是“结果合并”阶段,比如 UNION 把多段查询结果拼成一张表。
理解 id 的顺序,就像理解后厨的“流水线优先级”:编号是执行世界的时钟。如果执行计划里出现了 3、2、1 这样倒序的编号,别慌——那不是倒着执行,而是 3 号最内层的子查询最早开工,1 号主查询最后组装成品。很多新手在这里看反方向,结果分析错了瓶颈。
备餐步骤的先后理清了,接下来看每一行上贴的“任务标签”——它告诉你这一步到底在做什么。
- exec_order 执行序号表示第 i 个执行计划步骤在真实执行时的先后排名,数字越小越先执行。
- 主排序键 −idid 取负后参与排序,所以 id 越大排名越靠前,即内层子查询先执行。
- 副排序键 row_idx当多个步骤的 id 相同时,按照它们在 EXPLAIN 输出中出现的顺序从上到下执行,这里 row_idx 就是行号。
select_type查询类型
备餐卡上每个步骤旁边都贴着一个小标签,写着“配料”“腌制”“摆盘”之类的话。select_type 就是这个任务标签,它说明当前步骤在整条查询里扮演什么角色。标签不同,执行的策略也不同。
最常见的是 SIMPLE,表示这个 SELECT 没有子查询、没有 UNION,就是一道独立的菜。接下来是 PRIMARY 和 SUBQUERY:外层主查询标为 PRIMARY,被嵌套在里面的子查询标为 SUBQUERY。这就像“佛跳墙”这道主菜,需要先单独炖一盅高汤——高汤是 SUBQUERY,最后汇聚成佛跳墙是 PRIMARY。DERIVED 表示派生表:FROM 子句里的子查询结果会被当作临时表使用,相当于“先切好一盆配菜,再拿给主菜用”。UNION 相关标签则负责多段查询结果的拼接。
select_type 的价值不是让你背标签名字,而是帮助你建立查询的“嵌套地图”。看到 SUBQUERY 就知道这里有逻辑分层,看到 DERIVED 就要警惕——派生表往往意味着内存或磁盘临时表,行数大了可能成为性能瓶颈。它和 id 配合起来,整条查询的骨架就清晰了。
别忘了,后厨里除了明火炒菜,还有一套隐蔽的备料系统——用标签区分任务,本质是为了让不同的备料方式各司其职。而查询里“用哪种方式找食材”,才是决定速度的关键,这就是下一节的主角:type 访问路径。
type访问路径
同样是找一块五花肉,主厨有很多种办法:从贴着“五花肉”标签的抽屉里直接拿;沿着货架编号在指定区域翻找;或者干脆把整个冷藏库翻一遍。type 列描述的就是 MySQL 找数据的“方式”,它是执行计划里最重要的性能信号。
MySQL 官方把访问路径按性能从高到低排成了一架阶梯:
最顶端是 system 和 const,相当于“食材就摆在眼前,伸手即得”,前者是系统表单行,后者是根据主键或唯一索引定位到唯一一行。eq_ref 和 ref 是“按标签取食材”:前者在多表连接时用唯一索引匹配,后者用普通索引匹配到若干行。range 是有范围的扫描,比如 BETWEEN、> 这样的条件,相当于“在编号区间内翻找”。index 则比较有迷惑性——它虽然用了索引,但要顺着整个索引结构从头扫到尾,等于是“按目录页翻完整本书”。最底部的 ALL 就是全表扫描,把整张表逐行翻一遍,是性能杀手。
访问路径之所以关键,是因为它直接决定扫描成本。优化器估算代价时,大致遵循这样一个模型:
看到 type=ALL 的备餐卡,主厨心里会咯噔一下——这意味着要把整间厨房翻个底朝天。调优的目标,就是让访问路径尽量往上爬,从 ALL 爬到 range、ref 甚至 const。
- cost 总代价表示执行这一步的估算成本,成本越高查询越慢。
- rows 预估行数优化器预计要扫描的数据行数,来自表的统计信息。
- γ(type) 路径系数每种访问路径有各自的单位代价因子,ALL 系数接近 1,const 系数趋近于 0。
key索引使用
备餐卡上除了写“怎么找”,还会写“用哪把钥匙找”。key 这一列展示的就是查询实际使用的索引。在这之前还有一列 possible_keys,表示“候选钥匙串”——有哪些索引理论上可能被使用;而 key 才是优化器真正从钥匙串里抽出来的那一把。如果 possible_keys 有值但 key 是 NULL,说明有钥匙却没用上,通常是查询写法让索引失效了。
key 列给出了索引名,而 key_len 给出了这把“钥匙”的字节长度。关键技巧在于:索引长度越长,说明索引中被用到的列越多。比如一个复合索引 (a, b, c),如果 key_len 只算出 a 列的长度,说明只有 a 列被用于过滤,b、c 列没派上用场,索引利用率不高。
key_len 的估算规则与列的类型紧密相关。以最常用的 varchar 和 utf8mb4 字符集为例:
主厨看到钥匙串上挂了三把钥匙,但实际只用了一把——这提醒你:复合索引不是建了就完事,列的顺序和查询条件必须匹配。钥匙选得好不好,直接决定访问路径能爬多高。
- 求和符号对复合索引中被实际用到的每一列 j 依次累加字节数。
- l_j × 4第 j 列声明的最大字符数乘以 4,即 utf8mb4 下最坏情况的字节数。
- +2 变长开销varchar 是变长字段,需要额外 2 字节存储实际长度信息。
rows预估行数
备餐卡上每一项任务后面,老主厨都会估算“这一步大概要翻多少箱食材”。rows 列就是优化器对扫描行数的估值,它来源于表的统计信息——比如索引基数、表行数、数据分布。这个数字是理解查询成本的重要窗口。
估算的数学基础是“选择性”:索引列去重后值的个数与总行数的比值。选择性越高,说明该列区分度越好,同样的条件能过滤掉越多行。
注意一个重要事实:rows 是“预估”而非“实际”。统计信息可能过期,也可能因为数据分布不均而偏差很大。所以 rows 的价值不在于精确,而在于对比——对比不同索引方案的 rows,就能预判哪个方案扫描更少。当你看到 rows 接近表总行数,同时 type 又是 ALL,基本可以断定这条查询要把整张表翻个底朝天。
备餐卡上的行数估算,本质上在回答一个问题:“这份工作值不值得做?”如果预计要翻一万箱食材才能找到五份,聪明的厨师会换一种备料方式。这就是 rows 给优化器的信号——也给你优化方向的信号。
- COUNT(DISTINCT col)统计该列有多少个不同的值,相当于数一数抽屉里有多少种食材。
- COUNT(\*)统计表的全行数,即总共有多少份食材。
- 比值 selectivity两者相除,取值范围 0 到 1,越接近 1 表示每份食材都独一无二,过滤效果越好。
Extra额外信息
备餐卡的最后一栏,是主厨随手写的备注:“需借用隔壁灶台”“要临时搭一张桌子”“顺便把盘子也洗了”。Extra 列就是执行计划的备注栏,但这里的每条备注都可能是性能的彩蛋或地雷。
最值得开心的备注是 Using index:它表示查询所需的列全部包含在索引里,可以直接“读索引”而不用回表查数据,这正是覆盖索引的效果——相当于食材连包装盒都备好了,打开抽屉就能用。最常见的“地雷”是 Using filesort:MySQL 需要额外做一次排序操作,可能用到内存或磁盘文件,当排序的数据量很大时,性能急剧下降。另一个雷是 Using temporary:查询需要创建临时表来中间计算,常见于 GROUP BY 和某些 DISTINCT 场景,临时表可能落到磁盘上,代价更高。Using where 则表示存储引擎返回数据后,还要再过滤一遍条件。
判断是否需要回表,可以用这个逻辑理解覆盖索引的判定:
备餐卡上的备注栏虽小,却常常藏着决定成败的细节。Using filesort 一旦出现,哪怕 type 是漂亮的 ref,整体性能也可能被排序拖垮——就像备好了上等食材,却发现灶台不够用,只能干等。
- cols(SELECT) 查询列SELECT 后面要取出的字段集合。
- cols(WHERE) 条件列WHERE 条件里用到的字段集合。
- idx_cols 索引列集当前索引包含的所有列,两者并集被索引列集包含时,就用不上回表。
优化实战
备餐卡的每个栏目都读懂了,真正的功夫在于把它们串起来综合诊断。拿到一份执行计划,主厨会按三步走:先看 type 判断访问路径的“段位”;再看 rows 判断代价规模;最后扫一眼 Extra 看有没有隐藏操作。三者结合,就能定位性能瓶颈的源头。
假设一个典型的慢查询:用户订单列表按时间排序,EXPLAIN 显示 type=ALL、rows=100000、Extra=Using filesort; Using temporary。翻译成厨房语言就是:要把十万份食材全部翻一遍,还要临时搭桌子、额外排序——三重灾难叠加。优化动作通常是:为 (user_id, order_time) 建复合索引,让 type 变成 ref,rows 骤降,同时索引本身已经排好序,filesort 消失。
判断一条查询是否“危险”,可以把各条信号加权汇总:
优化不是一次性的,改完索引、调完写法,一定要重新跑一次 EXPLAIN 前后对比——就像改完备餐流程后,再让二厨按新卡走一遍,确认每一步都变快了。持续用这张备餐卡检验每一次查询改动,你就能把常见性能问题消灭在“上菜”之前。
- 𝕝[ALL] 全表扫描指示如果 type 是 ALL,这个指示函数取 1,否则取 0,贡献 100 分风险。
- 𝕝[temporary] 临时表指示Extra 里出现 Using temporary 时取 1,贡献 30 分风险。
- 𝕝[filesort] 排序指示出现 Using filesort 时取 1,贡献 20 分风险,三项相加得到总风险分。
AI 应用场景
慢查询自动巡检助手
阶段:数据诊断与异常检测
解决的问题:传统慢查询排查依赖 DBA 手动跑 EXPLAIN 逐条分析,面对成百上千条慢 SQL 效率极低。AI 助手自动抓取慢查询日志,批量对每条 SQL 执行 EXPLAIN,提取 type、rows、Extra 等关键信号,用风险评分模型自动筛选高危查询,并给出索引建议。
具体案例:某电商平台每晚大促后产生数千条慢查询,AI 巡检系统自动分析执行计划,识别出 30 条 type=ALL 的订单查询,自动生成“建议为 user_id+order_time 建复合索引”的报告,DBA 确认后一键执行,上线后平均查询耗时从 1.2 秒降到 40 毫秒。
查询性能回归防线
阶段:持续集成与质量门禁
解决的问题:业务代码上线前,新 SQL 可能悄悄引入全表扫描,人工 review 难以发现。AI 在 CI/CD 流水线中为每个变更涉及的 SQL 自动执行 EXPLAIN,将执行计划结构化为特征向量,与历史基线比对,发现 type 降级或 rows 异常膨胀时自动拦截发布。
具体案例:某金融系统在代码合并请求中集成 AI 检查器,开发提交了一条 WHERE status != 1 的查询,该写法会导致索引失效、type=ALL。AI 检测出访问路径从 range 降级为 ALL,风险分从 10 涨到 100,自动阻塞合并并提示改写为 status = 0,成功避免了一次生产事故。
索引推荐与调优顾问
阶段:智能决策与方案生成
解决的问题:索引设计依赖资深 DBA 经验,新手很难判断该建什么索引。AI 分析 EXPLAIN 输出中的 possible_keys 为 NULL 但 WHERE 条件频繁的列,结合 rows 的高估情况,自动推荐候选复合索引,并通过对比模拟执行计划验证效果。
具体案例:一个内容管理系统的文章表频繁按 category_id + created_at 查询,AI 分析执行计划发现 type=ALL、rows=500 万,推荐复合索引后,模拟 EXPLAIN 显示 type 提升为 ref、rows 降到 200,实际建索引后查询延迟下降 98%。
Python 演示
# -*- coding: utf-8 -*-"""简化版 EXPLAIN 风险分析器""" class ExplainRiskAnalyzer: def __init__(self, table_rows_map): self.table_rows_map = table_rows_map def type_factor(self, access_type): factors = { 'system': 0.002, 'const': 0.005, 'eq_ref': 0.02, 'ref': 0.05, 'range': 0.2, 'index': 0.6, 'ALL': 1.0, } return factors.get(access_type, 0.5) def analyze(self, plans): total_cost = 0 risks = [] for plan in plans: table = plan.get('table', '?') access = plan.get('type', 'ALL') rows = plan.get('rows', 0) extra = plan.get('extra', '') cost = max(rows * self.type_factor(access), 1) total_cost += cost signals = [] if access == 'ALL' and rows > 1000: signals.append('全表扫描且行数大') if 'Using filesort' in extra: signals.append('文件排序') if 'Using temporary' in extra: signals.append('临时表') if 'Using index' in extra: signals.append('覆盖索引(好)') risks.append((table, access, rows, cost, signals)) return risks, total_cost def main(): analyzer = ExplainRiskAnalyzer({'orders': 100000}) fake_explain = [ {'table': 'orders', 'type': 'ALL', 'rows': 100000, 'extra': 'Using where; Using filesort'}, {'table': 'users', 'type': 'eq_ref', 'rows': 1, 'extra': ''}, ] risks, total = analyzer.analyze(fake_explain) print('=== EXPLAIN 风险分析报告 ===') for table, access, rows, cost, signals in risks: print('表: %s, 路径: %s, 预估: %d, 成本: %.1f' % (table, access, rows, cost)) for sig in signals: print(' - %s' % sig) print('总估算成本: %.1f' % total) if total > 1000: print('结论: 存在明显性能隐患,建议优化') else: print('结论: 执行计划较健康') if __name__ == '__main__': main()
factors = {...}每种访问路径的代价系数表,对应正文里 type 七级阶梯的 γ(type)。cost = max(rows * self.type_factor(access), 1)用预估行数乘以路径系数估算成本,至少记 1 防止除零。if access == 'ALL' and rows > 1000:检测“全表扫描且行数大”的经典危险信号。if 'Using filesort' in extra:从 Extra 备注中识别文件排序隐患。risks.append((table, access, rows, cost, signals))把每张表的分析结果汇总成报告。
常见误区
误区看到执行计划里 type 是 index,就觉得“已经用到索引了,查询没问题”。
正解index 表示“全索引扫描”,虽然走的是索引结构,但要顺着整个索引从头扫到尾,本质上是“按目录页翻完整本书”,通常只比全表扫描稍好。真正代表高效的是 const、ref、range 这些有过滤能力的路径。判断好坏不能只看“有没有索引”,要看访问路径处在阶梯的哪一级。
为何常见:初学者容易把“用了索引”和“高效”划等号,忽略了 index 和 ALL 一样都是“扫全量”的本质。
误区以为 EXPLAIN 里的 rows 就是查询真实扫描了多少行数据。
正解rows 是优化器基于统计信息估算的预估值,不是实际值。统计信息过期、数据分布不均、多列关联估算偏差,都会让 rows 与实际行数相差甚远。它的价值在于“对比不同方案的相对代价”,而不是当作精确的计数器。
为何常见:人类天然相信数字,看到 rows 就以为它是“实测数据”,忽略了 EXPLAIN 本质是“计划”而非“结果”。
误区担心跑 EXPLAIN 会像执行 SELECT 那样实际运行查询,甚至修改数据。
正解在 MySQL 8.0 中,EXPLAIN 只负责生成执行计划并展示,不会真正执行 SELECT 部分,也不会修改任何数据。对于 UPDATE/DELETE 等语句,EXPLAIN 同样只做计划分析。真正会执行的是 EXPLAIN ANALYZE(MySQL 8.0.18+),它才需要实际运行并输出耗时信息。
为何常见:EXPLAIN 和 EXPLAIN ANALYZE 名字相近,加上部分老版本在分析子查询时可能有限制,容易让人产生它会“真跑”的误解。
误区做完 EXPLAIN 只盯着 type 列,看到 ref 或 const 就觉得万事大吉。
正解type 只是访问路径的“段位”,真正决定整体快慢的还有 rows 的规模、key_len 的索引利用率、Extra 里的 filesort 和 temporary。type=ref 但 rows 是 50 万,可能比 type=range 但 rows 是 100 的两条查询慢得多;一条 type=const 的查询如果带 Using temporary,也可能拖垮全局。必须把关键列组合起来综合判断。
为何常见:type 的七级阶梯直观好懂,容易让人产生“只要路径好看就够”的错觉,忽略代价由多个因素共同决定。
误区给查询条件涉及的列建好索引后,理所当然地认为执行计划会用到它,type 肯定漂亮。
正解索引不会“自动生效”。对索引列使用函数、隐式类型转换、前导模糊查询(LIKE '%xx')、优化器认为返回行数占比过大时,都可能放弃索引走全表扫描。另外,复合索引要遵循“最左前缀”原则,查询条件没从第一列开始,索引同样派不上用场。
为何常见:凭直觉以为“有索引 = 用索引”,忽略了优化器会根据成本决定走不走索引,也忽略了索引失效的多种场景。
面试题
Q1. 在 MySQL 中执行 EXPLAIN 的主要作用是什么?
- A.展示查询的执行计划与成本信号(12字)← 正确答案
- B.直接执行查询并返回数据结果(13字)
- C.自动优化并重写所有慢查询(12字)
- D.删除冗余索引以释放存储空间(13字)
答案:A。EXPLAIN 像主厨的备餐卡,只展示执行计划而非真正执行。它的输出包含 type、rows、Extra 等字段,用于预判查询性能。B 说的是直接执行,C 和 D 都超出了 EXPLAIN 的职责范围。
Q2. 以下哪种子查询的 select_type 代表“外层主查询”?
- A.SIMPLE 表示主查询(9字)
- B.PRIMARY 代表外层主查询(10字)← 正确答案
- C.SUBQUERY 用于主查询(9字)
- D.DERIVED 指向主查询(9字)
答案:B。在包含子查询的语句中,外层主查询标为 PRIMARY,内层子查询标为 SUBQUERY。SIMPLE 用于没有子查询的语句,DERIVED 是 FROM 子句里的派生表,SUBQUERY 是内层子查询。
Q3. EXPLAIN 输出中 rows 字段表示什么?
- A.实际扫描的数据行数(10字)
- B.预估扫描的数据行数(10字)← 正确答案
- C.表里的总数据行数(9字)
- D.返回给用户的行数(9字)
答案:B。rows 是优化器基于统计信息估算的扫描行数,不等于实际值。它由索引选择性、表行数和统计信息共同推算,用于对比不同方案的代价。A、C、D 都把它当成了确切的计数。
Q4. Extra 列出现 Using filesort 代表什么?
- A.在磁盘上创建了临时表(11字)
- B.需要额外的排序操作(10字)← 正确答案
- C.使用了文件系统缓冲(10字)
- D.索引覆盖了所有列(10字)
答案:B。Using filesort 表示 MySQL 要为查询结果做额外排序,可能用到内存或磁盘文件,是典型的性能隐患。它并不直接表示“创建临时表”,那是 Using temporary;也不表示用了文件缓冲或覆盖索引。
Q5. 哪些情况会导致 type 退化为 ALL 全表扫描?
- A.对索引列使用函数包裹(11字)← 正确答案
- B.索引列存在隐式类型转换(11字)← 正确答案
- C.查询返回表的大多数行(11字)← 正确答案
- D.索引列使用了覆盖索引(10字)
答案:ABC。对索引列使用函数(如 WHERE YEAR(col)=2024)和隐式类型转换都会让索引失效;优化器认为返回行数占比太大时,也会放弃索引走全表扫描。覆盖索引恰恰是高效利用索引的表现,不会导致 ALL。
Q6. 一个 utf8mb4 字符集的 varchar(100) 索引列,其 key_len 约为多少字节?
- A.约一百字节长度(8字)
- B.约两百字节长度(8字)
- C.约四百零二字节(8字)← 正确答案
- D.约四百字节长度(8字)
答案:C。utf8mb4 下每字符最多 4 字节,varchar(100) 的列内容最多占 100×4=400 字节,再加 2 字节记录变长长度,共 402 字节。理解 key_len 的构成,能帮助判断复合索引实际用到了几列。
Q7. 执行计划中 id 为 NULL 的行通常代表什么?
- A.查询结果集的并集操作(11字)
- B.一个被合并的物化子查询(11字)
- C.优化器引入的额外阶段(11字)← 正确答案
- D.被缓存不再执行的步骤(10字)
答案:C。id 为 NULL 的行不对应真实查询步骤,而是优化器引入的合并阶段,典型的例子是 UNION 的结果合并。它表示需要把前面几个 SELECT 的结果拼成一张表。A 过于狭隘,只描述了 UNION 一种情况;B、D 均不符合语义。
Q8. 关于 EXPLAIN 输出的 key 和 key_len,哪些说法正确?
- A.key 表示实际使用的索引(10字)← 正确答案
- B.possible_keys 列出候选索引(10字)← 正确答案
- C.key_len 越大索引利用越充分(10字)
- D.key 为 NULL 说明查询有覆盖索引(10字)
答案:AB。key 是实际选中的索引,possible_keys 是候选索引集合。key_len 表示索引列参与过滤的字节长度,越大通常说明用到的列越多,但它不一定代表“越充分就越好”,关键看查询是否需要。key=NULL 说明没用到索引,更谈不上覆盖索引,D 错误。
Q9. 对 UNIQUE 且 NOT NULL 的列做等值查询,type 通常是什么?
- A.const 常量定位(8字)← 正确答案
- B.ref 非唯一引用(8字)
- C.eq_ref 唯一引用(9字)
- D.range 范围扫描(8字)
答案:A。当查询能够通过唯一索引精确命中一行时,访问路径是 const,相当于“一眼锁定唯一食材”。eq_ref 是多表连接时按唯一索引取一行,ref 对应普通索引匹配多行,range 则是范围扫描。
Q10. 以下哪些执行计划特征组合说明查询存在明显性能隐患?
- A.type=ALL 且 rows 接近表总行数(15字)← 正确答案
- B.Extra=Using filesort 且无对应索引(16字)← 正确答案
- C.key=NULL 且 type=range(15字)← 正确答案
- D.rows 远小于实际扫描行数(13字)
答案:ABC。type=ALL、rows 大说明全表扫描且代价高;Using filesort 且无索引支撑,排序会落在临时文件上;key=NULL 说明想用索引却没找到合适的,range 也没发挥出索引优势。D 中 rows 是估算值,与实际情况有偏差属正常现象,不构成“性能隐患”的判据。