想象你走进一家藏书百万册的图书馆:没有检索目录时,管理员只能从第一排书架开始,一本一本地翻,直到找到你要的书——这就是数据库里的“全表扫描”。而一份井井有条的“书名→书架位置”目录,能让管理员三步五步直达目标,这就是索引。 但索引不是越多越好。目录建得乱(比如只记了作者忘了书名、按没法比较的方式记录),反而让管理员翻目录比翻书还慢;每建一套新目录,还得有人维护,占书架还费力气。这篇文章就用“图书馆借书”这条主线,带你搞懂:索引有哪几类、联合索引怎么排、最左前缀和索引下推是什么、哪些写法会让索引失效、前缀索引与唯一索引怎么选,以及索引日常怎么保养。 学完你会明白:把索引用好,查询快如闪电;把它用砸,写入变慢还白占空间。

岗位关联

对 AI 从业者而言,索引策略是数据工程的基石能力:无论是给特征表加速还是给日志库瘦身,会设计索引和不会设计索引,性能差距能有一个数量级。

目录
  1. 可视化演示
  2. 索引类型
  3. 最左前缀
  4. 联合索引设计
  5. 索引失效场景
  6. 索引下推 ICP
  7. 前缀与唯一索引
  8. 索引维护策略
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

通过三种模式展示B+树结构原理、联合索引最左前缀原则、以及索引条件下推ICP优化机制。

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

B+树索引结构 非叶子节点只存键值,叶子节点存数据 叶子节点通过双向链表连接,支持范围扫描 树高 = O(log n),IO次数少 B+树是MySQL InnoDB的默认索引结构

索引类型

图书馆里找书,方法不止一种。管理员手里可能备着好几套目录:一套按书名字母排的卡片柜,一套按作者姓氏排的,还有一套记录书里关键词的“内容索引”。数据库的索引也分好几种形态,各有各的脾气,选错目录就像让管理员用作者卡去查书号,事倍功半。

最常见的是 B+树索引:键值有序排列,既支持“找某一本”,也支持“找某一区间”;哈希索引像一排带锁抽屉,钥匙对上立刻开箱,但抽屉之间毫无顺序;全文索引则给正文建关键词倒排表,用来回答“哪几本书提到过机器学习”。InnoDB 里还藏着一个“自适应哈希”,自动为热门的等值查询加速。

B+树把 N 条记录组织成树高只有几层的结构。主键索引的叶子直接挂整行数据,二级索引的叶子只存索引键加主键,查到后还要“回表”取其余列。整表数据涨到亿级,B+树高度通常也只到 4 层,因此查询成本几乎恒定——这正是数据库选它当主索引的原因。

所以选索引类型要先想清楚借书习惯:精确知道书号,用哈希抽屉最快;想借某一区间的书,B+树目录才能沿字母翻;搜主题词,只能靠全文索引。没有一把万能钥匙,管理员的目录柜从来是“各司其职”。

h=log⁡bN\displaystyle h = \log_b N
树高就是查找时要翻的目录层数:藏书每翻十倍,树高只加一两层,所以几亿行数据依然能“三步定位”。
  1. N(记录总数)图书馆藏书的册数,数据行越多,可挑选的空间越大。
    N=表的行数N = \text{表的行数}
  2. b(扇出)每个目录节点最多能引出多少个子节点,由磁盘页大小与键长决定,InnoDB 中通常上百。
    b=⌊页大小键长+指针长⌋b = \left\lfloor \frac{\text{页大小}}{\text{键长} + \text{指针长}} \right\rfloor
  3. h(树高)从根目录走到叶子书架需要翻过的层数。N 亿级时 h 依然只有 3~4。
    h=log⁡bNh = \log_b N

最左前缀

图书馆有一套“作者→书名→出版年”的三联目录卡。管理员查张三的《SQL 入门》,先翻到“张”姓区,再在里面翻“三”,最后比对年份,一路顺读,目录很好用。可你要是甩给他一句“帮我查 2020 年出版的书”,他就得把整套卡片从头翻到尾——因为目录的第一关键字是作者,不是年份。

这个“必须从最左边那栏开始顺读”的规矩,就是联合索引的“最左前缀”原则:MySQL 只能从左往右连续利用索引列。对 (a,b,c) 联合索引,条件覆盖 (a)、(a,b)、(a,b,c) 都能命中;只写 b,或者写 a 和 c 却跳过 b,后面的列就再也用不到索引。

更隐蔽的是“范围列中断”:WHERE a=1 AND b>10 AND c=5 时,b 用了范围比较后,c 无法继续在索引树里定位,只能把 b 范围里的行全部回表再过滤。因此设计时要把等值查询的列放前面,范围条件尽量放最后,才能把每一列索引都榨干。

记忆口诀是“顺读目录、跳栏作废”:设计联合索引前,先把业务查询里最常出现的等值列排到最左,范围列垫底,这样管理员每一次翻卡都能顺到底。

idx(a,b,c):可用前缀: (a), (a,b), (a,b,c);(a,c)⇒仅 a 走索引\displaystyle \text{idx}(a,b,c):\quad \text{可用前缀: } (a),\,(a,b),\,(a,b,c);\qquad (a,c) \Rightarrow \text{仅 } a \text{ 走索引}
联合索引的“可用前缀”就是目录可以顺读的栏数:从第一栏连续往下翻有效,跳着栏翻等于作废。
  1. 联合索引 (a,b,c)按“作者→书名→年份”排好的三栏目录,键值先比 a 再比 b 再比 c。
    idx(a,b,c)\text{idx}(a,b,c)
  2. 可用前缀从最左列开始连续取 1 到 3 列都能定位,像目录从“作者”栏顺读。
    (a), (a,b), (a,b,c)(a),\,(a,b),\,(a,b,c)
  3. 跳列即断条件里跳过 b 直接用 c,c 只能等回表后再过滤,无法参与索引内的定位。
    (a,c)⇒仅 a 走索引(a,c) \Rightarrow \text{仅 } a \text{ 走索引}

联合索引设计

三联目录卡的列顺序为什么不能乱排?假设作者姓氏只有一千种,书名却有十万个。若目录第一列是“出版年”,你想查“某作者某书”,管理员就得在上万个年份分区里反复横跳,目录优势荡然无存。列的顺序,决定了管理员翻卡的速度。

设计联合索引有三个核心考量:区分度、覆盖、写入代价。区分度等于列的不同取值数除以总行数,越接近 1 越能一刀切中目标,应当靠左;查询涉及的列若全部被索引包住,就是覆盖索引,连回表都省了;但每加一列索引,写入时就要多维护一棵排序树。

冗余也要清理:已有 (a,b) 索引再建 (a,b,c),且 c 很少被查询使用,属于重复建设;而 (a) 和 (a,b) 不算完全冗余,前者能单独服务只查 a 的查询。实战中要结合慢查询日志统计高频条件,再决定“删哪建哪”。

设计联合索引就像整理衣柜:常穿的外套挂最外层,少用的塞进深处;每多一套目录,就多占一块书架。好索引不是“越多越好”,而是“刚好够用”。

区分度(col)=COUNT(DISTINCT col)COUNT(∗);查询列⊆索引列⇒免回表\displaystyle \text{区分度}(col) = \frac{\text{COUNT(DISTINCT } col)}{\text{COUNT}(\ast)};\qquad \text{查询列} \subseteq \text{索引列} \Rightarrow \text{免回表}
区分度是“这一栏目录能区分出多少本书”:区分度越高,越适合放在目录最前面;如果查询列被索引整个包住,连书都不用搬出来。
  1. COUNT(DISTINCT col)该列有多少种不同取值,像图书馆里有多少个不同的作者姓氏。
    COUNT(DISTINCT col)\text{COUNT(DISTINCT } col)
  2. COUNT(*)总行数,用来把不同取值归一化成比例。
    COUNT(∗)\text{COUNT}(\ast)
  3. 区分度趋近 1说明这一列筛选能力强,应排在联合索引最左。
    区分度(col)→1⇒靠左\text{区分度}(col) \to 1 \Rightarrow \text{靠左}
  4. 覆盖条件查询涉及的列全部包含在索引列里,就不必回表取整行。
    查询列⊆索引列⇒免回表\text{查询列} \subseteq \text{索引列} \Rightarrow \text{免回表}

索引失效场景

有时管理员手里明明有目录,却还是只能一本本翻——因为你的借书单把目录“写坏了”。比如你把书名写成“2024 年新版《SQL 入门》”,而目录按书名首字排序,开头变了,索引自然无从定位。数据库里,这类“写坏借书单”的操作每天都在发生。

最常见的三种失效写法:对索引列做函数运算(WHERE YEAR(created_at)=2024)、隐式转换(字符串列 phone 与数字比较,MySQL 会把列 cast 成数值,等于悄悄对列做了函数)、前导通配符 LIKE '%abc'。此外,索引列参与 OR 且另一侧无法走索引时,整体也常退化为全表扫。

判断是否失效有一把通用标尺:看条件是否“破坏列本身”或“无法确定起点”。LEFT(col,3)='abc' 用了函数,失效;col LIKE 'abc%' 起点明确,能走索引。隐式转换最阴险:varchar 列对数字比较,肉眼看着没问题,EXPLAIN 一查却是全表扫。

借书单要写得让目录读得懂:原样给列名、给确定前缀、别让数据库偷偷转型。目录再快,架不住单子写歪——这是排查慢查询时最先该检查的三处。

WHERE phone=13800000000  ⇒  WHERE CAST(phone AS NUMERIC)=13800000000⇒索引失效\displaystyle \text{WHERE } phone = 13800000000 \;\Rightarrow\; \text{WHERE } \text{CAST}(phone\ \text{AS}\ \text{NUMERIC}) = 13800000000 \Rightarrow \text{索引失效}
这一节讲的是“目录读法”:条件里只要动了列本身(函数、转型)或没有确定起点(前导通配符),目录就无从翻起。
  1. 隐式转换varchar 列与数字比较时,优化器把列先转成数值,等于对列做了一次函数。
    CAST(phone AS NUMERIC)=13800000000\text{CAST}(phone\ \text{AS}\ \text{NUMERIC}) = 13800000000
  2. 函数运算只要条件里套了函数,B+树就无法按原始键二分跳转。
    WHERE F(col)=v⇒索引失效\text{WHERE } F(col) = v \Rightarrow \text{索引失效}
  3. 前导模糊LIKE '%abc' 没有固定起点,无法在目录里“翻到那一页”。
    LIKE ’%abc’⇒全表扫描\text{LIKE '\%abc'} \Rightarrow \text{全表扫描}

索引下推 ICP

三联目录的潜力还能再榨一层。老办法是:管理员按目录找出 100 本候选书,一本本搬到柜台核对“出版社对不对”,错的再搬回去;索引下推(ICP)彻底改变流程——他在目录格里翻片的瞬间,就直接核对其余条件,只把达标的那几本抱出来。

ICP 是 MySQL 5.6 起 InnoDB 默认开启的优化:联合索引扫到最左列命中候选后,存储引擎先用索引里已有的其他列继续过滤,过滤达标才回表取整行。关键是,它不需要你新建任何索引,纯粹是执行引擎的“顺手过滤”。

它恰好补上了“范围列中断”的一半遗憾:WHERE name LIKE '张%' AND age>18 配上 (name, age) 索引后,age 无法参与定位,但 ICP 能在索引层先把 age>18 过滤掉,回表行数大幅减少。注意 ICP 对主键索引没意义(本来就不用回表),对排序/分组也没帮助。

相当于把借书台的“人工初筛”挪进了目录卡片里:原本要抱 100 本书出来看,现在翻目录时就筛剩 20 本。ICP 不用建索引、不用改 SQL,是数据库白送的一笔性能红利。

R回表=S候选×(1−f下推过滤)\displaystyle R_{\text{回表}} = S_{\text{候选}} \times (1 - f_{\text{下推过滤}})
索引下推把“搬书出来再核对”改成“在目录格里先筛一遍”,能省下大量回表 I/O,而且不用新建任何索引。
  1. S_候选索引扫描最左列后得到的候选行数,像目录筛出的候选书单长度。
    S候选S_{\text{候选}}
  2. f_下推过滤下推到存储引擎层的其余索引列条件能提前挡掉的比例。
    0≤f下推过滤≤10 \le f_{\text{下推过滤}} \le 1
  3. 回表量ICP 之后真正要回表取整行的数量,比例越小越省 I/O。
    R回表=S候选×(1−f)R_{\text{回表}} = S_{\text{候选}} \times (1 - f)

前缀与唯一索引

有些书名长得离谱,管理员不想为每本书抄全名做目录,只抄前五个字:这就是前缀索引。目录瘦身了,但《机器学习实战》和《机器学习导论》前五个字相同,翻到候选区后还得回书架确认完整书名——省了空间,多了几次确认。

前缀索引用 INDEX idx_email(email(10)) 只索引前 10 个字符,特别适合 email 这类超长但前缀区分明显的列。核心指标是区分度:COUNT(DISTINCT LEFT(col,n)) 除以 COUNT(*),尽量靠近完整列的区分度,一般取到 90% 以上即可,再长收益锐减。

唯一索引则相反,它强制列值全局不重复,像身份证号。它不只是约束,还给了优化器“最多返回一行”的强提示,等值查询可以提前终止。代价是每次写入都要做唯一性检查,写密集场景是实打实的开销。业务上本来唯一的列(订单号、手机号)就直接用唯一索引,一举两得。

前缀索引像“简写目录”,省纸但偶尔要回架确认;唯一索引像“身份证目录”,绝不重号但要办证维护。长文本选前缀、天然唯一选唯一——这是索引策略里最容易被忽略的两张牌。

D(n)=COUNT(DISTINCT LEFT(col, n))COUNT(∗)\displaystyle D(n) = \frac{\text{COUNT(DISTINCT LEFT}(col,\, n))}{\text{COUNT}(\ast)}
前缀长度就是在“目录占地”和“翻错次数”之间找平衡:D(n) 越接近 1,说明这几撇字已经能把书分得足够开。
  1. LEFT(col, n)只取列值前 n 个字符做索引键,像目录只抄书名的前五个字。
    LEFT(col, n)\text{LEFT}(col,\, n)
  2. DISTINCT 计数统计这些前缀一共有多少种,衡量“前缀目录”的区分能力。
    COUNT(DISTINCT LEFT(col, n))\text{COUNT(DISTINCT LEFT}(col,\, n))
  3. D(n) 接近 1前缀区分度接近完整列时,索引瘦身而误判回表率很低。
    D(n)→1⇒前缀长度足够D(n) \to 1 \Rightarrow \text{前缀长度足够}

索引维护策略

再好的目录,用久了也会乱:书被移架、卡片被撕角、新书没插回原位。数据库索引同样如此——频繁插入删除会产生页碎片,统计信息过期后优化器会“看走眼”,该走索引的查询偏偏选了全表扫。

维护三板斧:第一,碎片整理,OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB 重建表,碎片率高的索引顺序读性能明显回升;第二,保持统计信息新鲜,定期 ANALYZE TABLE 更新基数,让优化器重新“看得准”;第三,盯紧慢查询日志,揪出“应该走索引却没走”的 SQL。

巡检时还要删冗余:两个索引前缀相同、一个完全覆盖另一个,可以合并成一个;写多读少的表要控索引个数,每个索引都是写入路径上额外的一次排序。

图书馆都有“闭馆盘库”:核对卡片、修整书架、下架旧书。数据库也该有一份索引保养日历——周更表查碎片、月更表刷统计、按慢查询反馈调整联合索引。目录常新,查询才常快。

碎片率=1−已用数据页总分配页数\displaystyle \text{碎片率} = 1 - \frac{\text{已用数据页}}{\text{总分配页数}}
碎片率像书架上的空档:空档太多,管理员来回跑的路线就长了,定期盘库整理才能让目录与书架“严丝合缝”。
  1. 已用数据页真实存放行数据的页,删改后部分页会变空或半空。
    已用数据页\text{已用数据页}
  2. 总分配页数索引向磁盘申请的全部页,含碎片空洞。
    总分配页数\text{总分配页数}
  3. 碎片率偏高顺序读退化成随机读,应重建表或整理索引恢复连续。
    碎片率>0.3⇒重建索引\text{碎片率} > 0.3 \Rightarrow \text{重建索引}

AI 应用场景

电商订单表:联合索引让 P99 从 800ms 降到 12ms

阶段:数据建模与查询优化

解决的问题:订单表上亿行,客服和风控频繁按“用户+状态+时间”过滤。传统写法把范围列 created_at 放最左,导致状态列无法走索引,查询退化为全表扫。AI 化的解决方式是先用慢查询统计高频条件,再按“等值列在前、范围列在后”重排联合索引,并给热门聚合查询加覆盖索引。

具体案例:某电商把 user_id、status、created_at 重排为联合索引后,订单查询 P99 从 800ms 降到 12ms;同时把下单写入的额外排序压到可接受范围,活动大促期间数据库 CPU 保持平稳。就像给最常借书的读者做了专属目录。

日志平台:前缀索引加 ICP 给海量检索瘦身

阶段:数据采集与特征存储

解决的问题:request_id 是 64 位长字符串,日志量百万级时直接建完整索引,索引体积比数据还大。用前缀索引缩小索引体积,再配合 ICP 让状态码过滤发生在索引层,回表量大减。

具体案例:日志表对 request_id 建 12 字符前缀索引,查询“request_id 前缀 + 状态码”时,ICP 先把 80% 不匹配的行挡在索引层,排障检索与 ETL 抽取提速数倍。好比目录只抄书名前几个字,却依然能快速锁定书架区。

画像回填:慢查询治理救活训练数据流水线

阶段:特征工程

解决的问题:给用户表回填兴趣标签要 JOIN 多张明细表,关联列没建索引导致全表扫描,批任务一跑就是 40 分钟。通过慢查询日志定位未走索引的 JOIN,补充联合索引并定期更新统计信息。

具体案例:某推荐团队在用户表与行为表的关联列上补联合索引,并设置周级 ANALYZE 任务,批量回填从 40 分钟缩到 3 分钟,特征更新频率从每天一次提升到每小时一次。相当于给流水线换上了常新的检索目录。

Python 演示

python82 行
# -*- coding: utf-8 -*-"""SQL索引策略演示:全表扫描 vs 索引查找 vs 最左前缀 vs 索引下推"""import bisectimport randomimport time  def build_books(n):    """模拟图书馆书架:随机生成 n 条 (书号, 分类号) 记录"""    random.seed(42)    return [(random.randint(0, 10 ** 6), random.randint(0, 999)) for _ in range(n)]  def build_index(books):    """建立简化版B+树索引:按书号排序的键数组与原始行号映射"""    ordered = sorted(range(len(books)), key=lambda i: books[i][0])    keys = [books[i][0] for i in ordered]    return keys, ordered  def full_scan(books, target):    """全表扫描:一格一格翻书架"""    for i, (key, _) in enumerate(books):        if key == target:            return i    return -1  def index_lookup(keys, rows, target):    """索引查找:二分跳转,只比较约 log2(n) 次"""    pos = bisect.bisect_left(keys, target)    if pos < len(keys) and keys[pos] == target:        return rows[pos]    return -1  def leftmost_prefix(index_cols, query_cols):    """模拟联合索引(a,b,c)的最左前缀:返回命中前缀列数"""    hit = 0    for _, qc in zip(index_cols, query_cols):        if qc is None:            break        hit += 1    return hit  def icp_count(keys, rows, books, lo, hi, low, high):    """索引下推:索引层先过滤分类号区间,只统计回表行数"""    hit = 0    for pos in range(lo, hi + 1):        row = rows[pos]        if low <= books[row][1] <= high:            hit += 1    return hit  def main():    n = 200000    books = build_books(n)    keys, rows = build_index(books)    target = keys[len(keys) // 2]     t0 = time.perf_counter()    full_scan(books, target)    t1 = time.perf_counter()    index_lookup(keys, rows, target)    t2 = time.perf_counter()    print("full scan: %.4fs" % (t1 - t0))    print("index search: %.4fs" % (t2 - t1))     idx = ("author", "title", "year")    cases = [("张三", "SQL", 2020), ("张三", None, 2020), (None, "SQL", 2020)]    for query in cases:        print("query", query, "-> prefix columns:", leftmost_prefix(idx, query))     total = len(keys)    pushed = icp_count(keys, rows, books, 0, total - 1, 300, 700)    print("candidate rows: %d, rows to fetch after ICP: %d" % (total, pushed))  if __name__ == "__main__":    main()
目的:模拟图书馆借书的两种找法(全表扫描与索引查找),并演示联合索引的最左前缀命中规则与索引下推的回表过滤效果。
  1. ordered = sorted(range(len(books)), key=lambda i: books[i][0])按书号给行号排序,模拟建索引时对键值排序,相当于给图书馆贴上索书号。
  2. keys = [books[i][0] for i in ordered]抽出有序键数组,简化版 B+树目录。
  3. pos = bisect.bisect_left(keys, target)二分查找,像目录跳转,一次排除一半候选。
  4. for _, qc in zip(index_cols, query_cols):逐列检查查询条件,遇到缺列即停。
  5. if qc is None: break条件里跳过中间列,正是最左前缀“跳列即断”。
  6. if low <= books[row][1] <= high:在索引层先用分类号过滤,模拟 ICP 下推,只有命中者才回表。
  7. if __name__ == "__main__":入口守卫,避免 import 时误执行演示逻辑。

常见误区

误区既然索引用处这么大,我干脆把所有常用列都加上索引,一劳永逸,反正数据库会自动选出最好的那个。

正解索引就像书架旁边的目录卡:目录越多越占地方、越耗维护。每条索引都会拖慢 INSERT、UPDATE、DELETE,因为写数据时还要同步更新索引树。正确的策略是“查询收益 > 写入代价”才建,读写比高的表才值得多建索引。

为何常见:大家只看到索引加速查询的一面,却忽略了写放大和空间成本这两个隐性账单。

误区联合索引 (a,b,c) 只有 WHERE a=…… 才能用,否则索引就是废的,所以查询条件的列必须和索引列完全一致。

正解其实是从最左列开始“连续”匹配:WHERE a=1 AND b=2 能用前两列,WHERE a=1 能单独用一列;而 WHERE b=1 从中间开始,才用不上。判断标尺是“是否从最左列起连续覆盖”,而不是“是否包含第一列”。

为何常见:把“最左”误解为“必须有第一列”,忽略了前缀长度是可变的,就像目录可以从第一栏顺读一栏、两栏或三栏。

误区WHERE YEAR(created_at)=2024 这种写法很常见,数据库聪明得很,肯定能自动走索引。

正解对索引列做函数或运算后,列值的原有顺序被破坏,B+树无法按原键定位,相当于把目录卡上的字迹涂花了。改写为 created_at BETWEEN '2024-01-01' AND '2024-12-31' 才能用索引。

为何常见:SQL 看起来“差不多”,但优化器只能按索引树的键值比较,函数改变了键值形态,索引就再也派不上用场。

误区模糊查询不是天生就该用索引吗?我写 LIKE '%abc' 也是模糊查询,怎么 EXPLAIN 显示全表扫描?

正解LIKE 'abc%' 有确定起点,可以走索引;LIKE '%abc' 起点未知,只能全表扫。前缀索引的设计也依赖这一逻辑——给超长文本建前缀目录时,只有确定起点的查询才能享受加速。

为何常见:混淆了“后缀通配”和“前导通配”对起点定位的影响。在目录里翻页必须知道从哪一页开始,前导通配等于没有起点。

误区既然 ICP 是免费优化,那我把查询条件都塞进索引列,任何查询都能快得像闪电。

正解ICP 只对“需要回表的二级索引扫描”有效:它把无法用于定位、但索引里已有的列条件下推到存储引擎过滤。主键索引不需要回表,排序、分组、无 WHERE 的扫描都吃不到 ICP 红利。

为何常见:把“优化特性”当成“万能药”,没理解它的适用前提是“存在回表动作”。没有回表,自然无从下推。

面试题

Q1. 关于 B+树索引与哈希索引的对比,正确的是?

单选题 · easy

  • A.B+树支持范围查询且有序← 正确答案
  • B.哈希索引天然支持范围查询
  • C.B+树只能做等值匹配查询
  • D.哈希索引按顺序存放键值

答案:A。B+树叶子结点按键有序且以链表相连,天然支持范围扫描;哈希索引散列存放、无序,只适合等值查询。就像书名目录能沿字母逐页翻,而带锁抽屉只能对号开箱。

Q2. 联合索引 idx(a,b,c),执行 WHERE b=1 AND c=2 时,下列说法正确的是?

单选题 · mid

  • A.无法有效利用该联合索引← 正确答案
  • B.可以完整使用三列索引
  • C.只能使用 b 和 c 两列
  • D.索引下推可全部命中

答案:A。联合索引要求从最左列 a 开始连续匹配,b、c 都不在连续前缀上,因此无法有效利用该索引。MySQL 8 的 skip scan 属特例优化,常规教学以最左前缀为准。

Q3. 下列哪种写法会让索引列失效?

单选题 · mid

  • A.对索引列套用函数← 正确答案
  • B.对索引列做等值判断
  • C.用主键做范围查询
  • D.用常量做精确匹配

答案:A。对索引列套函数会破坏键值原始有序性,B+树无法按原键二分定位;等值、主键范围、常量匹配都不破坏索引结构。就像把目录卡上的字涂花,管理员就没法按笔画翻。

Q4. 覆盖索引带来的直接收益是?

单选题 · easy

  • A.免去回表取行← 正确答案
  • B.减少索引数量
  • C.增大索引空间
  • D.加速批量写入

答案:A。覆盖索引指查询列全部包含在索引键中,叶子页即可取数,无需再按主键回表。类似目录卡背面直接印了索书号,不用再去书架核对。

Q5. 联合索引 idx(a,b,c),下列能完整命中索引前缀的查询有?

多选题 · mid

  • A.等值匹配 a 和 b 两列← 正确答案
  • B.等值匹配 a 和 c 两列
  • C.等值匹配 a b c 三列← 正确答案
  • D.等值匹配 b 和 c 两列

答案:AC。命中前缀要求从 a 开始连续:A 命中 (a,b),C 命中 (a,b,c);B 跳过 b,只有 a 能走索引、c 无法定位;D 缺 a,整条用不上。目录必须从最左栏顺读。

Q6. 关于索引下推(ICP)的正确描述是?

单选题 · hard

  • A.能把过滤下推到存储引擎← 正确答案
  • B.主键索引上效果最显著
  • C.只对全文索引生效
  • D.会显著增加回表次数

答案:A。ICP 把无法用于定位但索引里已有的列条件下推到存储引擎过滤,减少回表;主键索引无需回表,所以没有增益;它也不是全文索引专属。

Q7. 下列哪些情形会导致索引失效?

多选题 · mid

  • A.字符串列与数字比较← 正确答案
  • B.使用前导通配符查询← 正确答案
  • C.使用覆盖索引查询
  • D.对列做日期函数运算← 正确答案

答案:ABD。字符串列与数字比较触发隐式转换、前导通配符没有确定起点、对列做函数都会破坏索引可用性;覆盖索引是合法使用索引的方式,不会失效。

Q8. 设计前缀索引时最看重的指标是?

单选题 · easy

  • A.前缀字符的区分度← 正确答案
  • B.索引占用的磁盘空间
  • C.前缀包含字母个数
  • D.写入时的排序次数

答案:A。前缀索引的目标是以尽量短的字符换来接近完整列的区分度,区分度高才能少回表。空间、字母个数、排序次数都不是首要指标。

Q9. 关于唯一索引的说法正确的有?

多选题 · mid

  • A.能强制列值不重复← 正确答案
  • B.等值查询可提前终止← 正确答案
  • C.写入时无需做检查
  • D.适合所有列都建立

答案:AB。唯一索引先保证列值不重复,再让优化器知道最多返回一行、可提前终止;但写入要做唯一性检查,所有列都建唯一索引会让写入路径代价翻倍。

Q10. 一张 5 亿行表的二级索引碎片率高达 40%,最合适的操作是?

单选题 · hard

  • A.重建表并整理碎片← 正确答案
  • B.删除所有二级索引
  • C.增加冗余联合索引
  • D.关闭慢查询日志

答案:A。碎片率 40% 说明页空洞严重,顺序读退化,最优解是 OPTIMIZE TABLE 重建整理;删索引、加冗余、关日志都会让后续查询更糟。