MySQL 索引核心原理

本文以 InnoDB 为默认存储引擎。索引的本质:用额外的空间和写入维护成本,换取更少的数据扫描、更少的磁盘 IO 和更稳定的查询性能。

1. 索引的物理基础:B+ 树

InnoDB 默认索引结构是 B+ 树。

B+ 树特点:

  • 多叉平衡树,不是二叉树。
  • 非叶子节点只存键值和子页指针,不存完整数据。
  • 叶子节点存数据或主键值。
  • 叶子节点之间用双向链表连接。
  • 所有叶子节点深度一致,查询路径长度稳定。

InnoDB 页默认 16KB。非叶子节点扇出很大,因此 B+ 树高度通常只有 2 到 4 层。一次查询通常只需几次页访问,磁盘 IO 可控。

B+ 树适合数据库索引的核心原因:

  • 范围查询高效:叶子链表可直接顺序扫描。
  • 排序高效:数据在索引内有序。
  • 磁盘友好:每次 IO 读取一个页,页内可放大量键值。
  • 高度低:减少随机 IO 次数。

对比:

  • 哈希索引:等值查询 O(1),范围查询和排序不支持。
  • B 树:非叶子也存数据,扇出较小,范围扫描不如 B+ 树。
  • 跳表:内存结构常见,磁盘数据库较少作为主索引。

2. InnoDB 的索引组织方式

2.1 聚簇索引

InnoDB 是索引组织表。聚簇索引即主键索引,叶子节点存储完整行数据。

如果表没有主键:

  • InnoDB 选择第一个非空唯一索引作为聚簇索引。
  • 如果没有合适的唯一索引,则生成 6 字节隐藏 row_id。

主键设计直接影响:

  • 二级索引大小:二级索引叶子存主键值,主键越长,所有二级索引越大。
  • 插入性能:随机主键容易造成页分裂。
  • 查询性能:主键定位最快。

自增主键的优势是顺序插入,减少页分裂,提高页填充率。随机主键或无序 UUID 会导致中间插入、页分裂和空间碎片。

2.2 二级索引

二级索引也叫辅助索引。叶子节点不存完整行,而是存:

  • 索引列值
  • 主键值

查询过程:

  1. 在二级索引中定位到索引列条件。
  2. 拿到主键值。
  3. 回聚簇索引查完整行。

这个过程叫回表。

回表是随机 IO 的主要来源之一。二级索引扫描出的主键可能离散分布,回表时访问的聚簇索引页可能不连续。

2.3 联合索引

联合索引按定义列顺序排序。

例如索引 (a, b, c),排序规则是:

  • 先按 a 排序
  • a 相同按 b 排序
  • b 相同按 c 排序

因此可用条件遵循最左前缀:

  • a = ?
  • a = ? AND b = ?
  • a = ? AND b = ? AND c = ?
  • a = ? AND b > ? 可用到 b
  • b = ? 无法直接使用该索引定位
  • c = ? 无法跳过 a、b 使用

范围条件后的列通常不能继续用于索引定位。例如 (a, b, c) 中:

sql 复制代码
WHERE a = 1 AND b > 2 AND c = 3

a 和 b 可用于定位,c 不能用于缩小 B+ 树扫描范围。

3. 关键执行机制

3.1 回表

二级索引拿到主键后,再查聚簇索引。回表次数多,随机 IO 多,性能下降。

减少回表的方式:

  • 使用覆盖索引。
  • 只查询必要列。
  • 调整查询条件,减少无效记录。

3.2 覆盖索引

查询需要的列全部在索引中,不需要回表。

例如索引 (a, b, c):

sql 复制代码
SELECT a, b, c FROM t WHERE a = 1 AND b = 2;

该查询可被索引覆盖。EXPLAIN 的 Extra 显示 Using index。

InnoDB 二级索引叶子包含主键,因此:

sql 复制代码
SELECT id, a, b FROM t WHERE a = 1;

如果 id 是主键,也可能被 (a, b) 覆盖。

覆盖索引减少回表,但不改变 MVCC 可见性判断。某些情况下仍需访问 undo 判断版本可见性。

3.3 自适应哈希索引

InnoDB 会为热点等值查询页建立自适应哈希索引,即 AHI。

特点:

  • 自动维护,不可直接干预。
  • 只适合等值查询。
  • 范围查询仍走 B+ 树。
  • 可能增加维护开销,高并发下有时被关闭。

3.4 页分裂与页合并

B+ 树页有容量限制。插入新记录时:

  • 顺序插入:追加到页尾,页分裂少。
  • 随机插入:可能插入已满页中间,触发页分裂。
  • 页分裂会产生新页,影响填充率,增加碎片。

删除记录通常先标记删除,不一定立即合并页。大量删除后可能产生空间碎片。重建表可整理碎片。

4. 优化器与执行计划

MySQL 优化器基于成本选择索引。成本包括:

  • IO 成本
  • CPU 成本
  • 扫描行数估算
  • 回表代价
  • 排序代价

优化器依赖统计信息:

  • 索引基数 Cardinality
  • 数据分布
  • 页数量

统计信息可能不准。ANALYZE TABLE 可更新统计信息。统计不准时,优化器可能选错索引。

EXPLAIN 关键字段:

  • type:访问类型。常见从好到差:system、const、eq_ref、ref、range、index、ALL。
  • key:实际使用的索引。
  • rows:预估扫描行数。
  • filtered:存储引擎返回后剩余条件过滤比例。
  • Extra:
    • Using index:覆盖索引。
    • Using where:Server 层过滤。
    • Using filesort:需要额外排序。
    • Using temporary:使用临时表,常见于分组或去重。

常见索引未使用场景:

  • 索引列上做函数或表达式:WHERE DATE(created_at) = '2024-01-01'。
  • 隐式类型转换:字符串列与数字比较。
  • 前导模糊匹配:LIKE '%abc'。
  • 联合索引不满足最左前缀。
  • OR 条件中部分列无索引。
  • 低选择性列,优化器认为全表扫描更便宜。
  • 范围过大,回表成本高于全表扫描。

5. 索引设计核心

5.1 联合索引列顺序

常见原则:

  • 等值条件列放前面。
  • 范围条件列放后面。
  • 排序、分组列结合最左前缀考虑。
  • 选择性高的列不一定无脑放最前,要结合查询模式。

例如查询:

sql 复制代码
WHERE a = ? AND b = ? ORDER BY c

索引 (a, b, c) 可同时支持过滤和排序。

如果查询:

sql 复制代码
WHERE a = ? AND b > ? ORDER BY c

索引 (a, b, c) 中,b 是范围,c 的全局有序性不能继续保证,排序可能仍需 filesort。

5.2 选择性

选择性 = 不重复值数量 / 总行数。越接近 1,过滤效果越好。

但单列选择性不是唯一标准。联合索引的选择性是组合结果。低选择性列放在联合索引前导,可能扫描大量记录。

5.3 前缀索引

对长字符串,可只索引前 N 个字符:

sql 复制代码
KEY idx_name (name(10))

优点:索引更小。

限制:

  • 无法覆盖完整列查询。
  • 通常无法支持 ORDER BY name。
  • 需要选择足够长前缀,使选择性接近完整列。

5.4 控制索引数量

索引不是越多越好。每个索引都带来:

  • 插入、更新、删除时的维护成本。
  • 存储空间。
  • 优化器选择成本。
  • 页分裂和碎片风险。

写多读少表尤其要控制索引数量。

5.5 主键设计

主键应尽量:

  • 短。
  • 有序。
  • 稳定,不频繁更新。

自增 bigint 是常见选择。分布式场景可用有序雪花 ID。无序 UUID 作为聚簇索引会导致随机插入和二级索引膨胀。

6. 边界与误区

  • 索引不改变数据可见性,MVCC 仍通过 undo 判断版本。
  • 覆盖索引避免回表,但不一定避免所有版本判断。
  • type=index 是全索引扫描,不等于高效。
  • Using where 不一定坏,Using filesort 也不一定坏,需结合数据量和排序量。
  • 唯一索引等值查询在记录存在时,InnoDB 锁可能退化为记录锁;普通二级索引加锁还会涉及主键锁。
  • 索引失效不是绝对规则,优化器基于成本决策。
  • force index 可临时验证,但不应作为长期设计。

7. 总结

MySQL InnoDB 索引的核心链路:

  1. B+ 树提供有序、低高度、范围友好的索引结构。
  2. 聚簇索引叶子存完整行,主键即数据。
  3. 二级索引叶子存索引列和主键,查询常需回表。
  4. 联合索引遵循最左前缀,范围列后的列通常不能继续定位。
  5. 覆盖索引用于减少回表,降低随机 IO。
  6. 优化器基于统计和成本选索引,可能选错。
  7. 索引设计围绕查询路径:过滤、连接、排序、分组、覆盖、回表成本。
相关推荐
浪潮IT馆1 小时前
Windows 10 安装 PostgreSQL 9.6.24 完整教程
数据库·windows·postgresql
谢亮_vipxieliang2 小时前
一套 Compose 搭起完整开发环境:MySQL + Redis + Nginx
redis·mysql·nginx·docker
Elastic 中国社区官方博客2 小时前
14 个 alerts,1 个 incident:使用 Elasticsearch 中的 ES|QL 衡量 alerting rule 噪声
大数据·运维·数据库·elasticsearch·搜索引擎·全文检索
资深技术分享员2 小时前
遗留系统——把“改不动的老系统“接过来
java·服务器·数据库
AllData公司负责人2 小时前
AllData数据中台物联网实时平台|集成 Apache StreamPipes,MQTT工业物联网数据实时预警实践案例
大数据·数据库·人工智能·物联网·apache·工业物联网·streampipes
天衍四九-2 小时前
Docker Compose企业实战系列(一):LNMP环境一键部署(Nginx\+MySQL\+PHP)
mysql·nginx·docker
在放️2 小时前
技术支持岗 · 笔试填空、问答题
数据库
oradh3 小时前
Oracle固定执行计划的方法----SQL Profie(二)
数据库·sql·oracle