MySQL基础:索引原理与优化

MySQL 索引是提升查询性能最直接的手段,但用不好反而会拖慢写入甚至导致全表扫描。这篇文章从底层数据结构出发,聊聊索引到底是怎么工作的,以及日常开发中最容易踩的坑。

B+树:InnoDB 索引的核心

InnoDB 默认使用 B+树作为索引的数据结构。与普通二叉树不同,B+树是一棵多路平衡搜索树,每个节点可以存放多个键值,树的高度通常只有 3~4 层。这意味着即使表中有上千万行数据,通过主键查找一条记录最多也只需要 3~4 次磁盘 I/O。

B+树的几个关键特点:

  • 所有数据都存储在叶子节点,非叶子节点只存储索引键值和指针,这使得单个页能容纳更多的索引条目,降低树的高度。
  • 叶子节点之间通过双向链表连接,天然支持范围查询和排序操作——这也是为什么 ORDER BY 主键时几乎不需要额外排序。
  • 聚簇索引与二级索引的区别:InnoDB 的主键索引(聚簇索引)叶子节点直接存储完整的行数据;而二级索引的叶子节点存储的是主键值,查询时需要先在二级索引中定位到主键,再通过主键索引找到完整行——这就是常说的"回表"。

索引的四种类型

  1. 主键索引(PRIMARY KEY):每张表只能有一个,不允许 NULL,InnoDB 会用它来组织整张表的物理存储。如果建表时没有显式指定主键,InnoDB 会选择第一个非空唯一索引,或者自动生成一个隐藏的 6 字节 ROW_ID。

  2. 唯一索引(UNIQUE):列值必须唯一,但允许 NULL。适合用在业务上有唯一性约束的字段,比如手机号、身份证号。

  3. 普通索引(INDEX):最基本的索引,没有任何约束。适用于频繁出现在 WHERE、JOIN 条件中的字段。

  4. 联合索引(Composite Index):在多个列上建立的索引,遵循"最左前缀"原则。例如 INDEX(a, b, c) 可以覆盖 WHERE a=1WHERE a=1 AND b=2WHERE a=1 AND b=2 AND c=3 的查询,但无法直接用于 WHERE b=2WHERE c=3

索引失效的常见场景

在实际开发中,以下几种写法会让索引形同虚设:

  • 对索引列使用函数或表达式WHERE YEAR(create_time) = 2020 无法走 create_time 上的索引,应改为范围查询 WHERE create_time >= '2020-01-01' AND create_time < '2021-01-01'
  • 隐式类型转换:字段是 varchar 类型却传入数字进行比较,MySQL 会对字段做隐式转换,导致索引失效。
  • LIKE 以通配符开头WHERE name LIKE '%张' 无法使用索引,WHERE name LIKE '张%' 则可以。
  • 联合索引不满足最左前缀:跳过了联合索引的第一个字段直接查后面的字段。
  • OR 条件中包含未索引列WHERE a=1 OR b=2,如果 b 没有索引,整个查询会退化为全表扫描。
  • NOT IN / NOT EXISTS:在某些情况下优化器会放弃使用索引。

用 EXPLAIN 诊断查询

遇到慢查询时,第一步永远是在 SQL 前面加上 EXPLAIN。重点关注以下几列:

  • type:访问类型,从优到劣依次为 system > const > eq_ref > ref > range > index > ALL。如果看到 ALL 就意味着全表扫描,需要优化。
  • key:实际使用的索引名,如果为 NULL 说明没走索引。
  • rows:MySQL 预估需要扫描的行数,越小越好。
  • Extra:额外信息。Using index 表示覆盖索引(不需要回表);Using filesort 表示额外排序;Using temporary 表示使用了临时表——后两者通常意味着有优化空间。

小结

索引不是越多越好。每个索引都会占用额外的磁盘空间,并且在 INSERT/UPDATE/DELETE 时需要同步维护。合理的做法是根据实际的查询模式来设计索引:优先为 WHERE、JOIN、ORDER BY 中高频出现的列建立索引,善用联合索引减少索引数量,并定期通过慢查询日志 + EXPLAIN 来持续优化。