数据库索引B+TreeMySQL聚簇索引查询优化EXPLAIN

数据库索引的本质:B+Tree 为什么是最佳选择?

从磁盘 I/O 到查询优化器,索引设计的完整心智模型

索引解决的根本问题

没有索引的全表扫描是 O(n) 的——对 1 亿行表执行一次查询可能需要几十秒。索引的本质是用空间换时间:维护一个辅助数据结构,把特定字段的查询从 O(n) 降到 O(log n)。

为什么不用 Hash 表?Hash 表的 O(1) 点查比 B+Tree 快,但不支持范围查询WHERE age BETWEEN 20 AND 30),也无法利用最左前缀原则。这是 B+Tree 成为数据库标配的核心原因。

B+Tree 的磁盘友好设计

B+Tree 的每个节点对应磁盘上的一个(Page,InnoDB 默认 16KB)。

  • 内部节点只存 key,不存 data → 扇出(fanout)极高,树高通常只有 3-4 层
  • 叶子节点存完整 data,且通过双向链表连接 → 范围查询高效
  • 查询任意行最多读取 3-4 次磁盘 I/O

对比 B-Tree(非 B+Tree):内部节点也存 data,扇出低,树更高,范围查询需要中序遍历。

聚簇索引 vs 二级索引

聚簇索引(Clustered Index)的叶子节点就是数据行本身,InnoDB 总是按主键建立聚簇索引。

二级索引(Secondary Index)的叶子节点存储的是主键值,而不是数据行。查询时需要两次查找:先在二级索引找到主键,再用主键去聚簇索引查完整行——这叫回表

覆盖索引(Covering Index):查询的所有字段都在索引中,无需回表。

-- 这个查询可以用覆盖索引,无需回表
SELECT email FROM users WHERE username = 'alice';
-- 前提:存在联合索引 (username, email)

联合索引的最左前缀原则

联合索引 (a, b, c) 实际上是按 a → b → c 排序的 B+Tree。

能用到索引的查询:

  • WHERE a = 1
  • WHERE a = 1 AND b = 2
  • WHERE a = 1 AND b > 2 ✓(b 之后的列无法用索引)

不能用到索引的查询:

  • WHERE b = 2(跳过了 a)✗
  • WHERE a LIKE '%abc'(前缀模糊)✗

索引下推(ICP):MySQL 5.6+ 可以在存储引擎层过滤不满足条件的记录,减少回表次数。

EXPLAIN 的核心指标

EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'paid';

关注这几列:

| 列 | 含义 | 警戒线 | |---|---|---| | type | 访问类型 | ALL(全表扫描)是危险信号 | | key | 实际使用的索引 | NULL 表示没用到索引 | | rows | 预估扫描行数 | 与实际数据量差距大时注意 | | Extra | 附加信息 | Using filesort / Using temporary 需优化 |

type 从好到差:system > const > eq_ref > ref > range > index > ALL

索引的代价

索引不是免费的:

  • 写放大:每次 INSERT/UPDATE/DELETE 都要维护所有相关索引
  • 存储占用:高基数列的索引可能占原表 30-50% 的空间
  • 优化器误判:统计信息陈旧时,优化器可能选错索引,用 FORCE INDEX 强制

索引设计原则

  1. 高频查询字段优先
  2. 区分度(cardinality)高的字段更适合建索引
  3. 频繁更新的字段谨慎建索引
  4. 联合索引优于多个单列索引(减少回表)