REJAVA / LEARNING NOTES
MySQL(关系型数据库)索引:选型与验证
用办件查询讲联合索引、最左前缀、回表、覆盖、下推与执行计划验证。
根据课程与真实学习交流整理;不是逐字稿
本次更新说明
- 整理为公开学习笔记,保留练习与隔日复测。
- 附课后问答:索引顺序与扫描区间、覆盖与回表、验证与代价。
- 注明 EXPLAIN ANALYZE 需要 MySQL 8.0.18 及以上。
热身 · 一 场景 · 二 找整行 · 三 联合索引 · 四 范围与覆盖 · 五 验证 · 六 应用 · 检验 · 完整表达 · 追问 · 隔日复测
学完能做到什么,为什么现在学
给定“按部门、状态和提交时间查办件”的查询,你能提出一个联合索引,沿着索引顺序说明它减少了哪些查找;能判断查询是否需要回表,并列出执行计划与实测需要核对的证据。
本课从“查哪批办件”开始,再讲内部结构。目标是把业务条件、索引选择和验证串成一段完整的解释。课件中的办件表是教学场景,不是真实项目的优化成果。一次真实作答中容易混淆的地方,整理在本课末尾的课后问答。
前置: 能读懂按条件查询、知道一行是一条办件记录即可;不要求先学 JVM(Java 虚拟机)的内容。 必学: 第一至六节、检验与表达。拓展折叠区可稍后看;篇幅较长时,第三节之后是合适的停顿点,再从第四节范围查询接着读。事务、锁和日志留到后续课。
主读: 官方 Multiple-Column Indexes(多列索引)。只读“姓名两列索引”的例子和最后四条查询,核对为什么索引的第一列很重要。其他依据集中在来源区。
热身:先回忆前几课(3 分钟)
先凭记忆回答,再展开核对。答不上来就记一句,接着往下学,这不是进入新课的门槛。
第8课:异步查询设置了结果超时,为什么仍要给远程调用设置超时?
结果超时让调用方不再等,但不会自动停止原任务。远程调用本身也要限制等待,避免工作线程长期被占用。见第8课超时。
第8课:exceptionally(异常兜底方法)、handle(结果与异常处理方法)、whenComplete(完成回调方法) 各在什么时候执行?哪个不改变原来的结果?join(等待完成方法) 失败时抛什么?
exceptionally(异常兜底方法) 只在出错时执行,返回替代值;handle(结果与异常处理方法) 成功、失败都执行,能同时拿到结果和异常;whenComplete(完成回调方法) 成功、失败都执行,不改变原来的结果,适合记日志。join(等待完成方法) 抛 CompletionException(完成异常),原始异常在 getCause() 里。见第8课异常。
第7课:4个消费者循环取任务。约定每个消费者取到结束标记便退出,生产者放完任务后要放几个标记?
放4个,每个标记只让一个消费者退出。队列暂时空不代表生产结束;阻塞取出会继续等。这里假设生产已结束、消费者正常处理,标记放在所有任务之后。见第7课结束协议。
一、先说业务:这条查询到底在找什么
“办件列表慢,你会直接给所有查询字段建索引吗?”
设想一个教学系统有100万条办件。经办人只想看“部门10、待审批、10月1日至8日之前提交”的记录。下面的表叫 case_item:id 是唯一办件编号,dept_id 是部门,status 是状态(0待审批、1已办结),submitted_at 是提交时间,applicant 是申请人。这些数字与字段是教学设定。
示例边界: 以下固定值用于阅读演示。应用接收部门和日期时应校验权限、类型与起止范围,并通过绑定参数传值;不要把输入拼进查询文本。新增索引会增加空间与写入成本,先在隔离测试表验证。[8]
SELECT id, dept_id, status, submitted_at
FROM case_item
WHERE dept_id = 10 AND status = 0
AND submitted_at >= '2026-10-01'
AND submitted_at < '2026-10-08';
演示:起点10月1日包含在内,10月8日不包含;10月7日任意时刻都在区间中。第六节给出6行数据,请先手工选出符合条件的编号,再看答案。本课不承诺这条查询的实际耗时。
index(索引)可以理解为“按某些字段排好的查找目录”。没有合适的索引时,可能要逐行检查;有了合适的目录,就有机会直接找到符合条件的那一段。full table scan(全表扫描)就是检查整张表的记录。这里的关键不是“索引名字出现了”,而是 查找范围有没有缩小,以及后续还需要读多少内容 。[3]
先说结论,再看“可以这样答”
我会先确认具体查询、返回字段和数据分布,再选索引。索引可以帮助定位一段记录,但每多一个索引都要占空间、维护写入,不能所有字段都单独建一个。还要比较修改前后的计划与实际执行表现。
二、先找编号,再找整行:回表是怎么来的
“主键索引和普通索引有什么区别?回表是什么?”
InnoDB(事务存储引擎)负责这张表的数据存储。我们显式把 id 定义为 primary key(主键):每条记录的唯一标识,不能为空。在这个前提下,它的 clustered index(聚簇索引)按主键组织记录,末端的页存放行数据。这里的 page(页)是一次组织多条记录的存储单位,不是网页。[2][7]
再为部门等字段建的 secondary index(二级索引),记录中保存“这些索引列的值+该行主键”。若查询还要申请人姓名,二级索引里没有这个字段,就先取出编号,再用编号到聚簇索引查整行。这个过程就是 table lookup(回表)。[2]
| 查询路径示意 | 拿到什么 | 下一步 |
|---|---|---|
| 按编号103查主键索引 | 编号103对应的整行数据 | 从这一行读取申请人 |
| 按部门、状态、时间查二级索引 | 部门10、状态0、10月3日、编号103 | 只要这些列就可以返回;还要申请人则按103回表 |
两次查找不等于固定两次磁盘读写:数据可能已经在内存中,一次树查找也可能涉及多个页。先把“查哪棵索引、要不要再取整行”说清楚,不背固定次数。
拓展:树形索引,先理解到哪一层?
把树理解成分层目录:上层帮助选择下一页,末端的 leaf page(叶子页)存索引记录。一个页容纳多条记录,查找不必逐行从头走。官方这里统称 B-tree(B树)索引结构,并明确索引记录在叶子页、默认页大小16KB。本课不要求推算树高、单页条数或背“固定三次磁盘访问”。空间索引和全文索引不属于本课讨论的普通索引。[7][3]
先自己说一遍,再看“可以这样答”
本例定义了主键,所以聚簇索引按主键组织整行数据。二级索引包含索引列和主键;查询要的字段不全在里面,就拿主键去聚簇索引取整行,这叫回表。是否回表取决于索引和查询所需字段,不能说二级索引一定要回表。
三、联合索引:先按部门,再按状态,最后按时间
“为什么用三列联合索引?最左前缀到底在说什么?”
composite index(联合索引)把多列按指定顺序放进同一个索引。候选是 (dept_id, status, submitted_at):先按部门排序;同一部门内按状态排序;同一部门、同一状态内再按提交时间排序。可以想成通讯录先按姓、再按名,单独的名在全册中未必挨在一起。[1]
| 部门 | 状态 | 时间 | 编号 |
|---|---|---|---|
| 10 | 0 | 09-30 | 101 |
| 10 | 0 | 10-01 | 102 |
| 10 | 0 | 10-03 | 103 |
| 10 | 0 | 10-08 | 104 |
| 10 | 1 | 10-02 | 105 |
| 20 | 0 | 10-03 | 106 |
这是索引记录的排序示意,年份均为2026,未展示真实页结构。查部门10,能定位到这个部门的一段;再限制状态0,段更小;最后限制时间,可以在这一段中找10月1日至8日之前的部分。
leftmost prefix(最左前缀)就是从索引第一列开始连续取:这里有“部门”“部门+状态”“部门+状态+时间”。它描述的是 索引定义中的列顺序 ,不是要求查询条件按这个顺序书写;把同样的等值条件前后交换,不会把索引本身重排。[1]
只给状态0、不限制部门时,状态0会散落在不同部门之下,无法直接按“从部门开始的一个连续前缀”定位。因此不能把这个候选当成专门按状态查询的等价替代。也不要说成“任何情况下都不会用这个索引”:可能扫整个索引,或使用后面拓展里说明的特殊优化。
对于本课这条查询,部门、状态都是等值,二者互换也可能形成有效候选。先放部门,是因为我们同时考虑“按部门看办件”的查询; 并不是永远把区分度最高的列放第一位 。还要看其他查询能否复用这个前缀,再用实际计划比较。[1]
先自己说一遍,再看“可以这样答”
联合索引不是把几个单列索引放在一起,而是按列顺序形成一个有序目录。本例先部门、再状态、再时间,可以先锁定部门和状态,再找时间区间。最左前缀看索引定义,不看条件书写顺序;部门放第一位还可以支持只按部门查询。
可以在这里暂停:先不背名词,只用上表解释“为什么状态0不一定全挨在一起”。下一节仍用同一条查询。
四、范围、覆盖与下推:分别减少哪一步
“范围查询后面的列是不是全失效?覆盖索引和索引下推一样吗?”
1. 先区分“缩小要扫的区间”和“在扫到的记录里继续筛选”
两个候选都可能沿索引查找。差别不是“先筛选还是先走索引”,而是索引圈出的区间是否已排除其他状态;没有排除的记录还要继续检查。
当前候选先固定部门10、状态0,再对时间做范围查找。假如换成 (dept_id, submitted_at, status),先固定部门10,再取10月1日至8日之前的时间区间,区间里可能同时有状态0和1。状态排在时间之后,这时通常不能继续用它把这个范围压成只含状态0的一个更小区间,但仍能对扫到的记录检查状态。[4]
所以准确说法是: 这里的时间范围限制了后续列参与区间构造,不代表后续列完全没用 。后面的状态还可能参与过滤,或者为查询提供返回字段。上述判断针对本课简单的等值+时间范围,不推成所有表达式都相同的口诀。
2. covering index(覆盖索引):需要的信息已经在索引里
第一节只返回编号、部门、状态和提交时间。三个业务列在二级索引里,编号作为主键也在里面,因此这个索引在字段上覆盖了查询:无需为了取得其他业务列再取整行。若加上 applicant,当前索引就不再覆盖。covering index(覆盖索引)是 查询与索引的关系 ,不是另一种固定树结构。[3][5]
不要为了覆盖所有查询把所有列塞进索引。索引变宽会占更多空间,插入、删除和相关更新也要维护索引。先确认页面究竟要哪些字段,尽量避免不必要的 SELECT *,再权衡收益。[8]
3. ICP(索引条件下推):先筛掉,再少取整行
假设查询还要申请人,且采用“部门、时间、状态”的候选。扫描时间区间时,索引中已有状态,存储引擎可以先检查状态0,排除状态1,再为通过的记录读取整行中的申请人。这就是 ICP(索引条件下推)要减少的工作: 仍可能回表,只是先过滤以减少回表 。是否采用由具体查询与执行计划确认。[9]
拓展:没有最左列条件,也可能做 skip scan(跳跃扫描)吗?
可能。优化器在满足限制、认为成本合适时,可以枚举前缀的不同值,为每个前缀做后续范围查找。8.4文档列有限制,例如单表、查询只涉及索引中的列等。它不是“最左前缀没有意义”,也不是无前缀条件必然很快。本课选型不依赖这种优化,遇到实际计划显示它再按官方章节核对。[4]
先自己说一遍,再看“可以这样答”
范围条件可能使后面的列不再用于缩小索引扫描区间,但后面的列仍可能过滤或覆盖查询。覆盖表示需要的列都在索引里;下推表示先在索引记录上判断条件,减少读取整行。两者减少的步骤不同,不能把下推当成完全不回表。
五、怎样验证:有候选,不等于已经优化成功
“索引建好了,你拿什么证明有效?”
optimizer(查询优化器)负责选择访问方案,会比较成本。小表或者一次要读大部分记录时,扫描表可能更合适。执行计划是选择的方案;实际执行是它在某次数据和环境上的表现。二者要分开看。[3][5]
先用 EXPLAIN FORMAT=TRADITIONAL(表格执行计划)看候选查询。显式指定格式,便于对照下表;字段全表不用一次背完。
EXPLAIN FORMAT=TRADITIONAL
SELECT id, dept_id, status, submitted_at
FROM case_item
WHERE dept_id = 10 AND status = 0
AND submitted_at >= '2026-10-01'
AND submitted_at < '2026-10-08';
验证思路:在测试表加索引前后分别记录计划,查询保持相同。不要为“看到理想输出”强制指定索引。本课没有在数据库上运行这条查询,以下只解释字段,不提供虚构输出。[5]
| 先看哪项 | 含义 | 别误读 |
|---|---|---|
key(选用索引) | 当前计划选择的索引 | possible_keys(候选索引)不等于最终选用 |
type(访问类型) | ALL全表扫描;index全索引扫描;range范围扫描;ref按非唯一键值等值访问 | 出现索引名仍可能扫整个索引;不只按类型给性能下结论 |
rows(估计检查行数) | 估计需要检查多少行 | 不是最终返回数,也不是实际扫描计数 |
Extra(补充信息) | Using index(索引覆盖读取)与Using index condition(索引条件下推)描述不同动作 | 前者不等于“扫描很少”;后者不等于“完全不回表” |
EXPLAIN ANALYZE(实际执行分析)会运行查询,再给出执行节点的实际耗时、返回行数和循环次数。它会产生真实执行负载,先在测试环境使用;每个节点的实际返回行数也不能一概叫“全表扫描行数”。本课先看估计与实际是否相差明显,不要求背完树状输出。[6]
版本注意:MySQL 8.0 官方文档写明 EXPLAIN ANALYZE(实际执行并分析) 是 MySQL 8.0.18 才引入的[10]。如果使用的数据库还是 5.7,或早于 8.0.18 的 8.0,就只能先看普通 EXPLAIN(查看执行计划的语句) 的估计值,再用慢查询记录、实际耗时对比来验证。具体以所用数据库的实际版本为准。
比较时保留同一查询、相近数据量和分布,并考虑缓存、并发和重复运行的波动;同时核对返回结果是否一致、写入维护成本是否可接受。这些是本课的验证方法,不是官方保证的提速比例。
先自己说一遍,再看“可以这样答”
我先确认计划选了哪个索引、怎样访问、估计检查多少行,以及是否覆盖或下推。再在可控环境看实际执行节点的行数与耗时,比较前后结果一致性和多次表现。索引名出现、估计行数下降,都不能单独证明线上已经变快。
六、一次完整应用:从6行数据推到候选索引
“给你一条慢查询和几行样例数据,你怎么推出候选索引,又怎么确认它对?”
使用第三节的6行表,假设每行另有不同的申请人姓名。完成下面三步后再看答案。这是索引顺序与结果的纸上演示,不是数据库性能实验。
- 第一节查询应该返回哪些编号?
- 在
(dept_id, status, submitted_at)和(dept_id, submitted_at, status)中,先选哪个作为候选?用105号说明区别。 - 现在页面要显示申请人姓名,原索引还覆盖吗?你会立即把姓名加进索引吗?
做完后核对应用答案
返回102、103。101早于起点,104正好等于被排除的终点,105状态不符,106部门不符。
先选部门、状态、时间:固定部门和状态后取时间范围,不必把105放进这个范围。“部门、时间、状态”先圈时间时会包含105,再检查它的状态。这是两种候选构造范围的区别;只有6行时优化器完全可能直接扫描表。
加姓名后不再覆盖,需要取整行中的姓名。先看返回量和访问成本,确认是否值得加宽索引;不能只为避免回表就把姓名加进去。
可选实践:已有隔离测试库时,如何验证候选?
在你自己的空白测试库建下列新表;如果同名表存在,先核对环境,保留报错,不覆盖或删除。只用虚构数据,固定表名,应用输入用绑定参数。
CREATE TABLE teach_case_item_0010 (
id BIGINT PRIMARY KEY,
dept_id INT NOT NULL,
status TINYINT NOT NULL,
submitted_at DATETIME NOT NULL,
applicant VARCHAR(80) NOT NULL
) ENGINE=InnoDB;
INSERT INTO teach_case_item_0010 VALUES
(101,10,0,'2026-09-30 12:00:00','甲'),
(102,10,0,'2026-10-01 00:00:00','乙'),
(103,10,0,'2026-10-03 12:00:00','丙'),
(104,10,0,'2026-10-08 00:00:00','丁'),
(105,10,1,'2026-10-02 12:00:00','戊'),
(106,20,0,'2026-10-03 12:00:00','己');
CREATE INDEX idx_dept_status_time
ON teach_case_item_0010 (dept_id, status, submitted_at);
验证思路:把第五节查询的表名换成本表,在创建索引前后各运行一次,返回编号集合都应为{102,103},无排序要求。再加上申请人列,观察覆盖关系与计划变化。6行只验证正确性与结构,不证明性能;需要足量、分布合理的测试数据才能比较性能。本段 SQL 未在示例环境中实际运行,请以自己测试库的结果为准。
七、检验:先关掉上面,凭记忆做
6 道题,每题三个选项字数相同。先回答,再展开核对;做完一次即可,不用刷到全对才继续表达。
- 本课二级索引中已有部门、状态、时间和主键,查询另外要申请人,通常还要做什么?
- A. 直接从索引取姓名
- B. 根据主键查找整行
- C. 根据状态计算姓名
查看答案与解释
答案:B
姓名不在当前索引里,需要按主键回表。第二节区分了索引中已有的信息和整行信息。
- 对本课联合索引,判断最左前缀首先看什么?
- A. 索引定义的列顺序
- B. 查询条件书写顺序
- C. 查询结果返回顺序
查看答案与解释
答案:A
看索引定义里的部门、状态、时间。交换等值条件的书写位置不改变索引排列。
- “部门、时间、状态”索引先按部门等值和时间范围扫描。后面的状态列怎样理解?
- A. 后续列值完全无用
- B. 保证缩小扫描区间
- C. 仍可参与条件过滤
查看答案与解释
答案:C
不继续参与这个范围的区间构造,不等于完全失效;仍可判断条件,是否采用下推看计划。
- 第一节查询没有申请人,三个业务列和主键全在当前二级索引里。字段关系上属于什么?
- A. 必须返回整行取值
- B. 当前索引覆盖查询
- C. 当前索引完全失效
查看答案与解释
答案:B
覆盖是相对于查询所需列而言。追加申请人后,当前索引就不再覆盖。
- 普通表格执行计划里的 rows(估计检查行数)=500,表示什么?
- A. 估计需要检查行数
- B. 实际已经扫描行数
- C. 最终必定返回行数
查看答案与解释
答案:A
它是估计值。真实执行需要另查实际执行信息,不能把500写成实测成果。
- 看到 Using index condition(索引条件下推),正确理解是?
- A. 所需字段全部覆盖
- B. 必定没有整行读取
- C. 先筛索引再读取行
查看答案与解释
答案:C
先判断索引里能检查的条件,再读取符合条件的整行。它与覆盖索引不同。
八、用完整回答检验理解
连续回答四问,总共 3 分钟左右。可以写下来,也可以先口述再整理成文字;选择题答对只证明能辨认,不自动代表能独立讲清楚。写完再对照各节的参考回答。
- 按部门、状态和时间区间查办件,你提出什么候选索引?为什么这样排?
- 现有二级索引保存部门、状态、提交时间及主键编号。①页面只显示这四项,是否还要按编号查整行?②页面再增加申请人姓名(索引中没有),需要多做哪一步?先答这两个小场景;范围后的状态过滤留到第四节单独复习。
- 有人说“用了索引,肯定快了”,你会补哪些验证?如果手上没有真实的优化数据,怎样如实说明?
- 举一类可能需要这样索引的业务查询,说出查询条件、候选索引和要注意的代价。可参考第九节末尾的候选场景。
练习:请在自己的笔记中写出答案,再展开参考内容核对。
九、常见追问
先答,再展开
部门、状态都是等值,状态放第一位一定错误吗?
不一定。对当前查询二者都可能形成候选。本课部门优先,是兼顾只按部门查询;需要结合其他查询和实际成本选。
扫描了整个二级索引,但没有回表,这一定快吗?
不能保证。覆盖减少取整行,但没有保证扫描记录少,要看规模和实际耗时。它仍可能比扫宽表便宜,但不能只凭覆盖下结论。
为什么不把申请人也加上,做到一次都不回表?
要权衡索引宽度、写入维护成本、返回量和实际收益。不是每次少量回表都值得新增或加宽索引。
6行测试表没有选择候选索引,能说设计一定错了吗?
不能。小表扫描可能成本更低。先验证结果、索引定义与查询关系,再用代表性的规模和分布比较。
手上没有真实的索引优化数据,怎样描述这个方案?
可以这样说明:“我能按查询条件提出候选并说明验证流程,但没有可确认的线上优化数据。这个办件表是练习场景。”不要把100万条教学假设或估计行数说成真实成果。
应用场景:可能用在哪里(候选)
四个候选场景,和一个必须会说的代价
- 办件列表按条件筛选、分页: 按部门、状态、提交时间查待办,就是本课的场景。先确认最常用的条件组合,再按“等值列在前、范围列在后”提出候选,用计划和实际执行验证。
- 按编号精确查找: 按办件编号、证件号查一条记录,是等值查找,单列索引就能定位;编号如果就是主键,直接走聚簇索引,不用回表。
- 统计报表按时间段汇总: 按提交时间范围统计,时间列可以构造范围。只统计索引里已有的列时,可能被索引覆盖,不用读整行。
- 按申请人姓名模糊查询: 官方文档说,LIKE 的参数是不以通配符开头的常量时,才能作为范围条件[4]。所以
LIKE '张%'能用姓名索引缩小范围,LIKE '%张%'不能。
必须会说的代价: 索引要占空间,插入、更新、删除时还要维护[8]。写入频繁的表(比如办件流转记录),不能每个查询都加一个索引。
十、隔日复测:明天换条件再答(约 5 分钟)
建议隔一天、开始新内容前用3分钟完成。约一周后再换一条查询复述;这些是复习建议,不阻挡新专题。
1. 候选改为(部门、状态、时间),只查部门20的全部状态。它有可用的最左前缀吗?
有,部门本身就是第一列前缀。是否最终采用还要看成本。
2. 查询只要编号、部门、状态、时间,现在又要求申请人。什么条件发生了变化?
索引不再覆盖所有所需列,需要读取整行中的申请人;不能自动断言该加列。
3. 计划显示估计检查80行,能说成“实际只扫描了80行”吗?
不能。估计与实际分开,教学推演与真实系统的结果也分开。要说实际情况,需要真实执行的结果和可确认的数据。
旧课复习:核心线程数4、最大线程数8、队列容量10。池里已有4个线程都在忙,队列里有3个任务。新任务来了,线程池怎么做?
线程数已到核心数4,队列还没满(3 < 10),所以先 放进队列 ,不新建线程;只有队列满了才会扩到最大线程数。第8课的隔日复测考的是“总数没到核心数”的分支,这次换另一个分支。此题复习第5课,不是本课新知识。
主来源与引用
本课末尾的课后问答:范围定位、覆盖与验证。
技术定义以8.4官方文档为准;本课的编号、办件数据、候选比较和验证步骤属于教学应用。索引速查表保留关键规则与易混点。
联合索引及最左前缀:第三节主读。
聚簇与二级索引:主键、行数据和二级索引中的主键。
索引用途:覆盖、小表和大比例扫描。
范围优化:多列区间构造与特殊跳跃扫描。
执行计划输出:字段含义、覆盖与下推的区别。
执行计划语句:实际运行分析。
索引物理结构:叶子页及默认页大小。
索引成本:空间及插入、更新、删除维护。
索引条件下推:先判断索引条件,再读取整行。
MySQL 8.0 执行计划语句:EXPLAIN ANALYZE(实际执行并分析) 自 8.0.18 引入。2026-10-06 核对。
学完后,可以沿同一个办件例子,用自己的话复述扫描区间、回表与覆盖的区别;卡住时回到第三节的排序表,找出是哪一行数据或哪一步访问路径没想通。下一课进入事务边界与隔离级别,范围定位与覆盖会在那里的热身中再回顾。
课后问答:理解是怎样变化的
问题与结论默认可见。展开后可看理解过程;以下根据真实交流整理,并非逐字稿。
查询条件是部门等值、状态等值、提交时间范围。联合索引按“部门、状态、时间”和按“部门、时间、状态”排列,差别在哪里?
最终结论: 把部门、状态两个等值条件放在前面,再按时间做范围查找,可以先定位到指定部门、指定状态的一段,再在其中找时间范围。时间排在状态前面时仍可能使用索引,只是时间区间里可能混入其他状态,需要继续过滤。
展开理解过程与易混点
最初理解(归纳): 曾提出“部门、状态、时间”的候选,也意识到时间靠前可能多查记录;但把差别描述成“时间在前,就先筛选再走索引”。
纠正与解释: 两种顺序都可能沿索引查找,差别不是“先筛选还是先走索引”,而是索引圈出的扫描区间里包含哪些记录。以课程样例为例:“部门、状态、时间”在部门10、状态0的记录中找10月1日至8日之前的时间区间,这个区间已排除状态1;“部门、时间、状态”在部门10中先取时间区间,105号办件(状态1、10月2日)就在区间里,还要继续检查状态才能排除。这是按排序推演,不是实测扫描次数。
最终结论: 把部门、状态两个等值条件放在前面,再按时间做范围查找,可以先定位到指定部门、指定状态的一段,再在其中找时间范围。时间排在状态前面时仍可能使用索引,只是时间区间里可能混入其他状态,需要继续过滤。
现有复盘未逐条保留中间追问,本页不补写对话。
二级索引里已有部门、状态、提交时间和主键编号。页面只显示这四项,与页面再加一个申请人姓名,访问上有什么不同?
最终结论: 只显示编号、部门、状态、时间时,所需字段都在索引里,构成 covering index(覆盖索引),不必为了额外字段再读整行。再加申请人姓名时,索引不再覆盖,需要按主键编号到聚簇索引取整行,这一步就是 table lookup(回表)。
展开理解过程与易混点
关键追问(归纳): 原题把“页面多要字段”和“范围后的状态过滤”挤在同一问里,“编号等四列”也不够明确,作答时指出题目看不懂;于是把问题拆成两种页面需求分别讨论,范围过滤另作一题。
纠正与解释: 判断依据是查询需要的列是否都在当前索引里。主键编号本来就保存在二级索引中;姓名不在索引中,就只能拿编号到按主键组织的 clustered index(聚簇索引)取整行。“范围后面的状态仍能过滤”是另一件事,与这两种页面需求无关。
最终结论: 只显示编号、部门、状态、时间时,所需字段都在索引里,构成 covering index(覆盖索引),不必为了额外字段再读整行。再加申请人姓名时,索引不再覆盖,需要按主键编号到聚簇索引取整行,这一步就是 table lookup(回表)。
加了索引、用 EXPLAIN ANALYZE(实际执行分析)看过计划,怎样才能说明这个索引确实有效?
最终结论: 用同一查询在修改前后比较实际执行节点的行数与耗时,核对结果一致,并考虑多次运行的波动;估计行数和实际行数分开说。索引名出现在计划里、估计行数下降,都不能单独证明已经变快。
展开理解过程与易混点
最初理解(归纳): 曾提出使用 EXPLAIN ANALYZE(实际执行分析)来验证,并指出用了索引、发生回表都不保证更快。
纠正与解释: 这个方向是对的,还需要落到可比较的证据上:对同一条查询,在修改前后比较实际耗时和执行节点的行数,并确认返回结果一致。EXPLAIN ANALYZE 的输出同时有估计信息和实际信息;实际部分的 rows(行数)表示该执行节点返回的行数,不能一概叫“全表实际扫描行数”。
最终结论: 用同一查询在修改前后比较实际执行节点的行数与耗时,核对结果一致,并考虑多次运行的波动;估计行数和实际行数分开说。索引名出现在计划里、估计行数下降,都不能单独证明已经变快。
现有复盘未逐条保留中间追问,本页不补写对话。
只有“按部门管理”“按状态查看”这类场景线索时,怎样提出具体的索引候选?加索引的代价应该怎么说?
最终结论: 可以这样表达:“如果常用查询是部门等值、状态等值、时间范围,我会先提出部门、状态、时间的候选,再看其他查询能否复用和实际效果。加索引也会增加空间及写入维护成本。”这是假设方案,需要用具体查询和验证结果支撑。
展开理解过程与易混点
最初理解(归纳): 曾只给出场景线索,没有说明具体查询;谈代价时概括为“计算开销”。
纠正与解释: 要选具体索引,还需要给出常用查询究竟按什么列等值、按什么列做范围、返回哪些列;WHERE(筛选条件)里有多个条件,并不意味着必然该建联合索引。代价也不只是“计算开销”:索引要多占空间,插入、删除和相关更新时还要维护索引。没有具体查询和验证结果时,不能把这些线索说成真实的优化成果。
最终结论: 可以这样表达:“如果常用查询是部门等值、状态等值、时间范围,我会先提出部门、状态、时间的候选,再看其他查询能否复用和实际效果。加索引也会增加空间及写入维护成本。”这是假设方案,需要用具体查询和验证结果支撑。
现有复盘未逐条保留中间追问,本页不补写对话。