mysql索引本质是基于b+tree的排序后快速定位结构,非“魔法加速器”;它通过预排序和树形组织实现o(log n)查找,显著优于全表扫描的o(n),但受最左前缀、函数操作等限制易失效。

索引本质是排序后的快速定位结构
MySQL索引不是“魔法加速器”,而是把无序数据预先排好序、再用 B+Tree 组织起来的查找路径。就像查字典不一页页翻,而是先看部首目录、再找页码——索引就是这张“目录”。它不存完整数据(二级索引只存列值 + 主键),但能用 O(log n) 时间定位到目标行,比全表扫描的 O(n) 快得多。10 亿行表查一条记录,B+Tree 通常只需 3–4 次磁盘 I/O;全表扫描则可能读取上万页。
B+Tree 是 InnoDB 的实际执行引擎
你建的每个 INDEX、PRIMARY KEY、UNIQUE,底层都是 B+Tree。它的关键设计直接决定查询是否高效:
- 非叶子节点只存键值和指针,不存数据 → 单页能装更多键 → 树更矮 → I/O 更少
- 所有数据都在叶子节点,且叶子节点用双向链表连成有序链表 → 范围查询(
BETWEEN、>=)、ORDER BY、GROUP BY都能直接走链表扫描 - 叶子节点存的是主键值(二级索引)或整行数据(聚簇索引)→ 这就引出“回表”问题:查
SELECT name FROM user WHERE email = 'a@b.com',若只有email索引,得先通过索引找到主键,再回聚簇索引查name字段
为什么有时加了索引也不快?常见失效场景
索引不是建了就生效。以下操作会让优化器放弃使用索引,退化为全表扫描:
- 违反最左前缀:对联合索引
INDEX idx_name_age (name, age),查WHERE age = 25不走索引;必须带上name或者用WHERE name LIKE '张%'才可能命中 - 在索引列上做计算或函数:
WHERE YEAR(create_time) = 2025→ 改成WHERE create_time >= '2025-01-01' AND create_time - 隐式类型转换:
user_id是INT,却写WHERE user_id = '123'→ 字符串强制转数字,索引失效 - 使用
!=、NOT IN、IS NULL(除非是唯一索引且允许空)等,优化器常认为选择率太高,干脆全扫
建索引前必须问自己的三个问题
别一上来就 ALTER TABLE ADD INDEX。先确认:
- 这列的
SELECT查询频次是否明显高于INSERT/UPDATE/DELETE?建议比值 ≥ 3:1,否则维护索引的写开销(每次 DML 都要更新 B+Tree)可能得不偿失 - 这个查询是否真的需要返回大量字段?如果是
SELECT id, name FROM user WHERE status = 1,考虑建覆盖索引INDEX idx_status_name (status, name),避免回表 - 字段区分度够不够?比如
gender只有 'M'/'F',建索引几乎无效;而email或order_no就很适合
真正难的不是“怎么建”,而是“该不该建、建在哪、建多宽”——B+Tree 的效率藏在数据分布和查询模式里,不是靠堆索引数量能解决的。










