想象你手里有一本按拼音排好的字典。想查“按”字,直接翻到拼音 àn 的区位,顺着书页的物理顺序两步就找到——这就是聚集索引带来的直觉:数据按某个顺序物理排好,查找时顺着这个顺序走。可如果只记得偏旁“扌”呢?你得先翻开末尾的部首检字表,查到“按”字在第 288 页,再翻到第 288 页去读那个字——这就是非聚集索引与回表的完整路径。数据库里的 MySQL InnoDB,天天在上演这本字典的查找逻辑:一张表的数据按主键物理排列,这是聚集索引;而你额外建的每一个普通索引,都是那本“检字表”。看懂这两条路径的差异,你就掌握了索引设计的半壁江山。本文用这本“会讲道理的字典”贯穿始终,带你从存储结构、查询路径、适用场景到建表策略,一次搞清楚聚集索引与非聚集索引的相爱相杀。

岗位关联

读懂聚集索引与非聚集索引,你就能看明白执行计划里的回表与覆盖,写出让 MySQL 快上十倍的建表与查询方案——这是后端与 AI 工程师驾驭海量数据的基本功。

目录
  1. 可视化演示
  2. 索引基本概念
  3. 聚集索引原理
  4. 非聚集索引原理
  5. 核心差异对比
  6. 适用场景分析
  7. 建表选择策略
  8. 覆盖索引优化
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示聚集索引与非聚集索引对比的核心机制,通过架构/数据/状态/性能四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

聚集索引与非聚集索引对比:聚簇索引 聚簇索引的物理存储与数据组织机 非聚簇索引的二级查找与回表机制 核心机制可视化 聚集索引与非聚集索引对比整体架构与关键组件

索引基本概念

如果让你在一本 1000 页的字典里找一个字,一页页翻叫全表扫描;从目录入手按拼音迅速缩小范围,则是索引的思路。数据库的索引,就是这本字典的“目录体系”——它不是复制数据,而是建一棵有序的树,让查找从“逐页翻看”变成“按图索骥”。这棵树的学名叫 B+ 树,你可以把它理解为:每一层都替你排除掉绝大多数不相关的页。

B+ 树从根节点出发,每个节点对应磁盘上的一个页。点查时,每层根据键值大小选择下一层分支,走对数级别层数就能到达目标。扇出(fan-out)指每个节点能挂的子节点数,由页大小和键大小决定。页越大、键越短,扇出越大,树越矮,磁盘 I/O 越少。

假设页大小 16KB、每个键占 16 字节,扇出可达 1000,三层树就能装下十亿行数据。也就是说,索引让“大海捞针”变成了“三次跳跃”——这就是加对了索引,慢查询能快几个数量级的根本原因。

回到那本字典:你翻目录时并不需要读完每一页,每一层目录都替你划掉一批不可能的区域。数据库的 B+ 树同样是这个道理——以有序的结构,换指数级的查找效率。记住:索引的本质,是拿空间换时间、拿有序换快速。

T查询=O(log⁡扇出N)\displaystyle T_{查询} = O(\log_{扇出} N)
通俗理解:这是一次索引点查的“走路步数”,每走一层就甩掉大量无关数据,所以只和树的高度有关。
  1. T查询一次索引查找的磁盘 I/O 次数,即 B+ 树从根到叶子的层数。
    T查询\displaystyle T_{查询}
  2. log扇出对数运算,扇出是每页最多能岔开的子节点个数,log 表示每层按键值排除无关分支。
    log⁡扇出\displaystyle \log_{扇出}
  3. N表的记录总数,N 增大时 log 增长非常缓慢,这是索引在海量数据下依然高效的原因。
    N\displaystyle N

聚集索引原理

字典正文按拼音从 a 到 z 从头排到尾,每个字在书里的物理位置由拼音决定:“啊”在第一页,“安”在第八页。这种“物理排布 = 逻辑顺序”的设计,就是聚集索引的灵魂。数据往哪儿放,完全由主键说了算。

在 MySQL InnoDB 里,整张表的行数据就存放在主键这棵 B+ 树的叶子节点上。叶子节点按主键从小到大排列,页与页之间也是这个顺序。叶子存的不是指针,而是完整的一行——所有列都在这间“屋子里”。所以按主键取行,相当于走到字典里那个字所在的房间,推门即见。

每张表只能有一个聚集索引,因为磁盘上只能有一种物理顺序。你没建主键时,InnoDB 会挑第一个非空唯一索引当聚集索引;连唯一索引都没有,就隐式生成一个 6 字节的 rowid。一个事实随之而来:主键选什么,几乎决定了整张表的物理命运。

字典拼音正文与聚集索引是同一个原理:谁知道主键(拼音),谁就能直达正文(数据)。物理顺序听谁的话,谁就是那本字典的“骨架”。

T聚簇点查=log⁡扇出N\displaystyle T_{聚簇点查} = \log_{扇出} N
通俗理解:按主键查一次搞定,不用第二次“翻页找字”,这是数据库里最便宜的查询路径。
  1. T聚簇点查按主键做等值查询(如 WHERE id = ?)时的磁盘 I/O 次数,一步到位拿到整行。
    T聚簇点查\displaystyle T_{聚簇点查}
  2. log扇出 N与上一节的树深度公式相同,说明聚集索引点查只付出一次 B+ 树查找的代价。
    log⁡扇出N\displaystyle \log_{扇出} N

非聚集索引原理

只记得偏旁,不会拼音怎么办?翻到字典末尾的部首检字表,按“扌”加四画找到“按”,条目上只写着“按,见第 288 页”——检字表不等于释义,它是一张线索卡。

非聚集索引(也叫二级索引、辅助索引)正是这张线索卡。它是独立于主键 B+ 树的另一棵树,键是你指定的索引列,叶子节点存两样东西:索引列的值 + 主键值。索引列决定它在辅助树里的位置,主键则是回正文的钥匙。

一次完整的按辅助列查找分两步:先在辅助树里按索引列找到主键,再拿主键回到聚集索引树取完整行。第二步就叫回表。因此非聚集索引点查通常付出约两次树查找的 I/O:一次查“检字表”,一次翻“正文”。

检字表给了你第二条路,但代价是多翻一次页;非聚集索引给了任意列快速定位的能力,但代价是多一次回表。真正的高手,会想办法让这“多翻的一次页”越来越少——这就是后面覆盖索引要做的事。

T非聚簇=log⁡扇出N+log⁡扇出N=2log⁡扇出N\displaystyle T_{非聚簇} = \log_{扇出} N + \log_{扇出} N = 2\log_{扇出} N
通俗理解:查非聚集索引像看检字表得到页码,再翻到那一页读字,总是“两跳起步”。
  1. 第一个 log先在辅助索引 B+ 树里按索引列定位,找到主键值。
    log⁡扇出N\displaystyle \log_{扇出} N
  2. 第二个 log拿着主键回到聚集索引树再定位一次,取到完整行,这就是回表。
    log⁡扇出N\displaystyle \log_{扇出} N
  3. 2log两次树查找相加,就是一次典型“索引查 + 回表”的总 I/O 代价。
    2log⁡扇出N\displaystyle 2\log_{扇出} N

核心差异对比

把字典正文和检字表摆在一起,会发现四种性格差异。正文一行是一个字的完整条目,检字表一行只有线索;正文只有一份,检字表可以按部首、笔画、四角号码各做一份;往正文插一个“新字”得按拼音挤进中段,往检字表追加只需要在末尾加一行;翻开正文就能读,而检字表读完还得翻正文。

翻译成数据库术语:聚集索引叶子存完整行、每表一个、写入需维护物理顺序、查一次即得;非聚集索引叶子存“索引键 + 主键”、每表可建多个、写入只维护自己的树、查询通常需回表。

写入代价的差异最易被忽视。向聚集索引插入一行,若主键落在已有数据中间,需要找页内空隙,空间不够就要页分裂,造成额外 I/O 和碎片。所以随机主键(UUID)让聚簇索引苦不堪言,自增主键则永远在最右边追加,近乎零分裂。

正文负责“按序直达”,检字表负责“另辟蹊径”。数据库设计者的任务就是决定谁当正文、谁当检字表,并承担相应的写入代价——天下没有免费的索引,每一份灵活都标好了价格。

C插入=C定位+E[页分裂与移动]\displaystyle C_{插入} = C_{定位} + E[\text{页分裂与移动}]
通俗理解:插入成本 = 找到座位 + 可能要挪动一排人。主键乱序等于每次都在“坐满的中间排”加塞。
  1. C插入向带聚集索引的表插入一行的总成本。
    C插入\displaystyle C_{插入}
  2. C定位在 B+ 树中定位插入位置的开销,每次插入都逃不掉。
    C定位\displaystyle C_{定位}
  3. E[页分裂与移动]期望的页分裂与数据移动代价。主键随机时该项显著增大,自增时近似为零。
    E[页分裂与移动]\displaystyle E[\text{页分裂与移动}]

适用场景分析

查“安”用拼音正文最快;列出一串“扌”旁的字,检字表加回表也还算优雅。但要说王牌场景,还得是“列出所有读音为 an 的字”——拼音正文连续存放,数据库像翻书一样顺序读出几十页,页与页零跳跃,这是聚集索引的独家本领。

聚集索引擅长三件事:主键范围查询(如 id BETWEEN 100 AND 200 物理连续,磁盘顺序读极快);主键排序(天然有序,ORDER BY 免额外排序);高基数主键等值查询。非聚集索引则擅长“查询列恰好在索引里”的覆盖场景,以及给高频 WHERE 条件提供快速入口。

反过来,若 WHERE name = ? 频繁出现而 name 不是主键,就得建非聚集索引。此时回表次数等于命中行数 M,每行都可能是一次随机 I/O。M 很大时,走辅助索引加回表未必比全表扫描快,优化器会自己权衡。这也是“选择性高”的列更适合建索引的原因。

选索引像选交通工具:在一块连续区域里取数,聚集索引像地铁,轨道固定、顺路高效;零散抽查,非聚集索引像打车,门到门灵活,但每单都要为“回表”的等待买单。先看清查询是“扫一片”还是“点几笔”,再决定让谁站 C 位。

C范围查询=log⁡扇出N+⌈M每页行数⌉\displaystyle C_{范围查询} = \log_{扇出} N + \left\lceil \frac{M}{\text{每页行数}} \right\rceil
通俗理解:范围查询先花一次定位,然后顺着页“连续扫货”,读 100 行只翻约 2 页,非常划算。
  1. log扇出 N定位起始键的树查找开销。
    log⁡扇出N\displaystyle \log_{扇出} N
  2. M 除以每页行数再向上取整范围内的数据连续存放,读 M 行只需读约 M 行所占页面数,顺序 I/O 被合并,所以范围查询在聚集索引上性价比极高。
    ⌈M每页行数⌉\displaystyle \left\lceil \frac{M}{\text{每页行数}} \right\rceil

建表选择策略

主编开印前先定正文排序规则,数据库建表前要先定主键。业界共识是自增整数主键优于 UUID:自增让新行永远追加在 B+ 树最右侧,插入不插队,页分裂极少;UUID 随机无序,新行常落在树中间,页分裂和碎片不断,就像往字典中间乱塞新字,每插一次都要重排周围几十页。

辅助索引不是越多越好。每多一棵二级 B+ 树,插入、更新、删除都要同步维护,写入变慢、空间暴涨。取舍准则是:低写高读多建索引,高写低读慎建索引。唯一性约束(手机号、身份证号等)用唯一索引一举两得——既保证不重复,又提供查找路径,但要接受写入校验的开销。

联合索引讲究列序。KEY idx(a, b) 先按 a 排、再按 b 排,能加速 WHERE a=? 与 WHERE a=? AND b=?,却几乎无法单独加速 WHERE b=?。这就是“最左前缀”原则。设计时把区分度高的列放最左边,同时让 SELECT 的列尽量落在索引里,为覆盖索引留后路。

就像主编拍板“正文按拼音排、检字表只做辅助”,你建表时的一句 PRIMARY KEY 或 KEY,已经为未来千万次查询写好剧本。多花两分钟想清楚主键与列序,胜过日后加班救慢查询。

索引选择性=DISTINCT(候选列)COUNT(∗)\displaystyle \text{索引选择性} = \frac{DISTINCT(\text{候选列})}{COUNT(*)}
通俗理解:选择性 = 这列能分出多少种“不同”。越接近 1,说明每个值几乎只对应一行,索引越精准。
  1. DISTINCT(候选列)该列去重后的取值个数,比如“性别”只有 2,“邮箱”可能有几十万。
    DISTINCT(候选列)\displaystyle DISTINCT(\text{候选列})
  2. COUNT(*)表的总行数。
    COUNT(∗)\displaystyle COUNT(*)
  3. 比值越接近 1说明该列一行一个值,区分度高,索引能精准排除大量无关行,最适合当索引列或联合索引首列。
    DISTINCT(候选列)COUNT(∗)→1\displaystyle \frac{DISTINCT(\text{候选列})}{COUNT(*)} \to 1

覆盖索引优化

有位聪明的读者发现,某些检字表条目会顺带标注“按,àn”。如果他要的恰好只是读音,在检字表上瞄一眼即可,压根不用翻到 288 页。“检字表即答案”的小技巧,在数据库里叫覆盖索引。

覆盖索引的判定:一次查询所需的所有列,都能在某个非聚集索引的叶子节点里找到。例如 KEY idx_name_score(name, score) 能覆盖 SELECT score FROM t WHERE name=?,因为 name 和 score 都在索引里;但 SELECT * 就覆盖不了,星号会引入主键之外的列,必须回表。

免回表让大量随机 I/O 消失,查询从“两次树跳”变成“一趟辅助树”。另一个相关优化是索引下推 ICP:MySQL 把 WHERE 中能在索引层过滤的条件下推给存储引擎,叶子扫描时先过滤再回表,进一步减少回表次数。构造“够用不过度”的联合索引,是 DBA 最常用的提速手段。

好的索引设计,就是让数据库在“检字表”阶段就能回答你的问题。哪天遇到慢查询,先别急着加内存,低头看看这条 SQL 是不是一次次在“翻页回表”——改成覆盖索引,往往一行改动,快上十倍。

免回表条件:查询列集合⊆索引列集合\displaystyle \text{免回表条件}: \text{查询列集合} \subseteq \text{索引列集合}
通俗理解:查询想要的“全部信息”索引里都有,就不用再回正文翻页——索引直接给出答案。
  1. 查询列集合SELECT 后面的列,以及 WHERE 里参与判断的列,构成“这次查询必须看到的全部信息”。
    查询列集合\displaystyle \text{查询列集合}
  2. 索引列集合该索引叶子节点里实际能提供的列,即索引定义的所有列。
    索引列集合\displaystyle \text{索引列集合}
  3. ⊆子集关系。当查询需要的东西索引全都有,就不必再回聚集索引拿“正文”了。
    ⊆\displaystyle \subseteq

AI 应用场景

电商订单分页与报表拉取

阶段:数据存储与查询优化(数据工程)

解决的问题:传统上主键用随机串,订单表几千万行后范围查询变成随机 I/O,按用户查最近订单更是逐条回表。用自增主键做聚集索引,配合 (user_id, create_time) 联合非聚集索引,让查询在索引层完成排序,只回表少数行。

具体案例:某电商订单表 5000 万行,线上 SQL WHERE user_id = ? AND create_time > ? ORDER BY create_time DESC LIMIT 20 原来 780ms;改为 KEY idx_user_time(user_id, create_time) 后降到 4ms,因为 user_id 与 create_time 都在索引内,排序被索引天然承担,回表只有 20 行。

推荐系统行为日志索引

阶段:特征工程与数据管道

解决的问题:推荐模型每天要按 user_id 拉取最近 100 条行为序列,又要按 item_id 统计热度,一张行为表两种访问模式靠主键无法同时满足。

具体案例:行为表 (id, user_id, item_id, behavior, ts) 建自增主键,另建 KEY idx_user_ts(user_id, ts) 与 KEY idx_item(item_id, behavior)。前者让“拉用户行为序列”按序取数并只回表少量行,后者让“按 item 统计行为类型”成为覆盖查询免回表,特征管道耗时下降约 60%。

特征宽表的列裁剪

阶段:离线训练数据准备

解决的问题:特征宽表动辄几百列,训练抽样 SELECT 大量列时,走辅助索引每次命中都要回表拿整行,I/O 浪费严重。

具体案例:某风控团队把 12 个高频特征连同主键组成联合索引,训练 SQL 只 SELECT 这些列,覆盖索引命中后不再回表,特征抽取时间从 210ms 降到 45ms,离线数据管道的整体吞吐提升近 4 倍。

Python 演示

python52 行
# -*- coding: utf-8 -*-"""模拟聚集索引与非聚集索引的查询路径, 对比回表成本."""import time  class Table:    """一张小表: 主键有序存储(聚集索引), name 列建有辅助索引(非聚集索引)."""     def __init__(self, n=20000):        # 数据行按主键顺序物理存放, 模拟 InnoDB 聚簇索引        self.rows = [{'id': i, 'name': 'user_' + str(i * 7919 % n),                      'score': (i * 97) % 100} for i in range(n)]        self.pk_count = 0        # 辅助索引: 键为 name, 值是主键列表, 叶子只存主键        self.name_idx = {}        for row in self.rows:            self.name_idx.setdefault(row['name'], []).append(row['id'])     def lookup_by_pk(self, pk):        # 按主键取行: 直接访问物理位置, 等价于聚集索引一次树查找        self.pk_count += 1        return self.rows[pk]     def lookup_by_name(self, name):        # 走非聚集索引拿到主键, 再回表取整行        ids = self.name_idx.get(name, [])        return [self.lookup_by_pk(i) for i in ids]     def lookup_by_name_covering(self, name):        # 覆盖索引: 只返回索引里已有的主键, 不产生回表        return self.name_idx.get(name, [])  def bench(times, fn, *args):    t0 = time.perf_counter()    for _ in range(times):        fn(*args)    return (time.perf_counter() - t0) * 1000  if __name__ == '__main__':    t = Table()    name = 'user_1234'    rows = t.lookup_by_name(name)    print('name 命中行数:', len(rows), ' 累计回表次数:', t.pk_count)    t.pk_count = 0    ms_cover = bench(5000, t.lookup_by_name_covering, name)    ms_normal = bench(5000, t.lookup_by_name, name)    ms_pk = bench(5000, t.lookup_by_pk, 0)    print('聚集索引点查(主键):   %.2f ms' % ms_pk)    print('非聚集索引 + 回表:     %.2f ms' % ms_normal)    print('覆盖索引(免回表):      %.2f ms' % ms_cover)
目的:用一张 2 万行的内存表模拟三类查询路径,直观对比“聚集索引点查”“辅助索引加回表”“覆盖索引免回表”的耗时差异。
  1. self.rows = [{'id': i, ...} ...]数据行按主键顺序物理存放,模拟 InnoDB 聚簇索引的叶子页。
  2. self.name_idx = {}构建 name 列上的辅助索引:key 是名字,value 是主键列表,等价于辅助索引叶子存“索引键 + 主键”。
  3. return self.rows[pk]按主键直接取行,就像按拼音翻到字典正文那一页,一次到位。
  4. ids = self.name_idx.get(name, [])先走辅助索引查到主键集合,这是“查检字表”那一步。
  5. return [self.lookup_by_pk(i) for i in ids]逐个主键回表取整行,演示了“翻页码找字”的回表过程。
  6. ms_cover = bench(5000, t.lookup_by_name_covering, name)覆盖索引路径只返回索引里已有的主键,不回表,耗时通常最低。

常见误区

误区给 name 建了非聚集索引,查 name 的 SELECT 就不需要回表了吧?我以为辅助索引的叶子里也存着完整的一行,和聚集索引差不多。

正解非聚集索引的叶子只存“索引列的值 + 主键值”,不存整行。查 SELECT * 或任何不在索引里的列,都必须拿主键回聚集索引取完整行。只有查询列全部落在索引列集合里(覆盖索引)才免回表。就像部首检字表只写页码、不写释义,想读释义必须翻正文。

为何常见:“索引”听起来就是把表复制一份排好序,初学者容易忽略叶子节点到底存了什么这个关键区别。

误区既然可以给多个列都建索引,那每个索引都可以是“聚集”的吧?建五个索引就有五种排列方式。

正解数据在磁盘上只能有一种物理排列顺序。InnoDB 的聚集索引每表最多一个(通常就是主键),其余都是非聚集索引,只是独立维护的 B+ 树,叶子里存“键 + 主键”,并不存放完整行数据。就像同一本字典的正文只能按一种规则排,检字表再多,正文只有一份。

为何常见:从“逻辑上能排序”去理解,而不是“物理上如何排布”,混淆了两类索引的存储本质。

误区查询慢就加索引,加了一堆辅助索引后,发现插入和更新越来越慢,还以为是数据库坏了。

正解每多一个非聚集索引,就多一棵写入时要同步维护的 B+ 树。订单表高峰期每秒几千次插入,每个索引都是额外的写放大。索引是用写入和空间换查询速度,高频写表要克制,低写高读的表才适合多建索引。

为何常见:只看到查询加速的收益,没看到写入链路上同步维护多棵树的隐含成本。

误区UUID 做主键也能建索引,查询照样走 B+ 树,为什么大家都说不要用?

正解聚集索引要求数据按主键物理有序。UUID 完全随机,新行落入 B+ 树中间的概率极高,频繁触发页分裂、页碎片与随机 I/O;而自增主键只在最右追加,几乎零分裂。往字典正文中间乱插新字,每插一次就要重排周围几十页,就是这个代价。

为何常见:不理解“物理顺序”是聚集索引的根本约束,以为索引结构对所有主键类型一视同仁。

面试题

Q1. 在 MySQL InnoDB 中,聚集索引通常建立在哪个列上?

单选题 · easy

  • A.任意挑选一列
  • B.主键所在列← 正确答案
  • C.首个非空的列
  • D.任意唯一索引

答案:B。InnoDB 的表就是一棵以主键为序的 B+ 树,主键即聚集索引。没有主键时才会退而求其次选第一个非空唯一索引或隐式 rowid。就像字典正文的排序规则由主编先定死——数据库的正文顺序由主键说了算。

Q2. 关于非聚集索引的叶子节点,下列说法正确的是?

单选题 · mid

  • A.存该行的整条记录
  • B.存索引列与主键值← 正确答案
  • C.存数据页物理指针
  • D.存哈希码及其值

答案:B。非聚集索引叶子存“索引列值 + 主键值”,作用是当线索卡,指引查询回聚集索引取完整行。它不存整行,也不是物理指针或哈希码,就像部首检字表只写页码而不写释义。

Q3. “回表”指的是什么?

单选题 · easy

  • A.把索引页读回内存
  • B.用主键回表取数据行← 正确答案
  • C.把整行数据写回磁盘
  • D.重建已经失效的索引

答案:B。走辅助索引找到主键后,还要拿主键回到聚集索引 B+ 树中定位完整行,这一步叫回表。它对应字典场景里的“翻到检字表给的页码再读字”——多一次树查找,多一次 I/O。

Q4. 一张 InnoDB 表最多能建几个聚集索引?

单选题 · mid

  • A.一个都建不了
  • B.最多只能一个← 正确答案
  • C.最多允许两个
  • D.限制数目为零

答案:B。数据在磁盘上只能有一种物理排列顺序,所以聚集索引每表最多一个,通常就是主键。其他索引都是非聚集索引,可以建多个。如同字典正文只能按一种规则排列,检字表却可以有好几份。

Q5. 聚集索引在范围查询上更快,主要原因是?

单选题 · easy

  • A.数据按主键物理有序← 正确答案
  • B.叶子节点体积更小
  • C.底层使用哈希加速
  • D.索引页全部在内存

答案:A。聚集索引的叶子按主键连续存放,范围查询只需定位一次起点,然后顺序读取相邻页面,磁盘顺序 I/O 极快。就像按拼音连续翻十几页就能读完所有 "an" 的字,中间不需要跳转。

Q6. 已知 SELECT score FROM t WHERE name = ?,name 上建有非聚集索引,而 score 不在索引中,查询路径是?

单选题 · hard

  • A.扫描聚集索引全部行
  • B.走 name 索引再回表取 score← 正确答案
  • C.直接读取覆盖索引结果
  • D.先全表扫描再行过滤

答案:B。name 有索引,可以走辅助索引快速定位;但 score 不在该索引里,索引叶子没有 score,必须拿主键回聚集索引取这一行的 score。这正是“检字表只给页码,读到完整释义还得翻正文”的场景。

Q7. 以下哪些场景更适合利用聚集索引/主键有序性?(多选)

多选题 · mid

  • A.按主键范围分批抽取← 正确答案
  • B.按主键做精确点查← 正确答案
  • C.频繁修改主键的值
  • D.按主键排序出报表← 正确答案

答案:ABD。主键范围抽取和主键排序都吃“物理有序”的红利,主键精确点查一次直达。而频繁修改主键会让聚集索引反复移动数据行、触发页分裂,代价极高,应当避免——就像反复把字典里的字换位置重排,成本巨大。

Q8. 关于覆盖索引,下列说法正确的是?(多选)

多选题 · hard

  • A.查询列必须全在索引里← 正确答案
  • B.所有查询都不用再回表
  • C.能减少磁盘随机 I/O← 正确答案
  • D.只能建立在主键之上

答案:AC。覆盖索引要求 SELECT 与 WHERE 涉及的列都被索引覆盖,此时查询在辅助索引叶子层就能拿全答案,免回表、减少随机 I/O。但它只对“恰好覆盖”的查询生效,不是所有查询都免回表;覆盖索引建立在普通列上即可,与主键无关。

Q9. 建表策略中,为什么推荐自增整数主键而非随机 UUID?

单选题 · mid

  • A.随机 UUID 读取效率更高
  • B.自增主键顺序插入少分裂← 正确答案
  • C.UUID 占用的存储空间更小
  • D.UUID 无法建立辅助索引

答案:B。自增主键让新行永远追加在 B+ 树最右侧,插入不插队,页分裂极少;UUID 随机无序,新行常落在树中间,页分裂和碎片不断。如同往字典中间乱塞新字要重排整片区域,而往末尾追加只需写下一页。

Q10. 从“字典”类比看,以下映射正确的有哪些?(多选)

多选题 · extreme

  • A.拼音正文对应聚集索引← 正确答案
  • B.部首检字表对应辅助索引← 正确答案
  • C.检字表页码对应索引叶子主键← 正确答案
  • D.翻到页码读字对应覆盖索引

答案:ABC。拼音正文按拼音物理排列,对应聚集索引;部首检字表独立于正文,对应非聚集索引;检字表写页码,对应辅助索引叶子存主键。翻到页码读字是“先查线索再回正文”,对应回表而非覆盖索引——覆盖索引是在检字表上直接拿到答案,不用翻页。