索引是数据库(以及搜索引擎等)中一个极其核心且重要的概念。理解索引是进行数据库性能优化的基础。
1. 索引是什么?
想象一下一本厚厚的《新华字典》。
- 没有索引的情况 :如果你想查找一个汉字,比如"锁",你只能从第一页开始,一页一页地翻,直到找到它为止。这在计算机里叫做 全表扫描,数据量越大,查找速度越慢,效率极低。
- 有索引的情况 :你会使用字典前面的"拼音检字表"或"部首检字表"。这个检字表就是 索引。它按照特定的顺序(拼音或部首)列出了所有汉字,并告诉你每个汉字在字典正文中的页码。你通过检字表快速定位到"锁"字,然后直接翻到那一页。这个过程非常快。
总之,数据库索引就是数据库中一张"目录"或"检字表",它存储了数据表中某一列或多列的值,并记录了这些值对应数据行的物理位置(或指针)。通过索引,数据库可以不必扫描整张表,就能快速找到所需的数据。
2. 为什么需要索引?
索引的核心目的只有一个:极大地提高数据查询的速度。
索引的优点:
- 大大加快数据检索速度:这是索引最主要的好处。对于建立了索引的列,查询速度可以提升几个数量级(从秒级到毫秒级)。
- 保证数据唯一性:通过创建唯一性索引或主键索引,可以确保数据库表中某列(或几列组合)的值是唯一的。
- 加速表与表之间的连接 :对用于连接的列(通常是外键)创建索引,可以显著提高
JOIN
操作的效率。 - 减少排序和分组的时间 :如果查询中的
ORDER BY
或GROUP BY
子句的列上有索引,数据库可以利用索引的有序性,避免额外的排序操作,从而提高效率。
索引的缺点:
索引不是万能的,它也有明显的代价,理解这些代价是正确使用索引的前提。
- 占用磁盘空间:索引本身也是一个文件,需要占用物理存储空间。数据量越大,索引占用的空间也越多。
- 降低写操作的速度 :当对表中的数据进行增加(
INSERT
)、删除(DELETE
)和修改(UPDATE
)时,不仅要操作数据行,还要同时更新对应的索引文件,以维持索引的最新状态。这会增加写操作的耗时。写操作越频繁的表,索引的维护成本就越高。 - 创建和维护耗时:创建索引和后期维护(如索引重建)都需要时间。
所以, 索引是用 空间 和 写性能 来换取 读性能。
3. 索引的底层原理(为什么这么快?)
索引之所以快,是因为它使用了高效的数据结构。最经典、最常用的索引数据结构是 B+树。
B+树索引
B+树是一种多路平衡查找树,是为磁盘等存储设备设计的一种数据结构。
B+树的结构特点:
- 多叉树结构:相比于二叉树只有两个子节点,B+树的每个节点可以有多个子节点。这使得树的"高度"非常低。例如,一个百万级数据的表,其B+树索引的高度可能只有3-4层。
- 所有数据都在叶子节点:非叶子节点只存储键值和指向下一层节点的指针,不存储实际的数据。所有的数据记录都存储在叶子节点中。
- 叶子节点形成有序链表:所有的叶子节点通过指针连接成一个有序的双向链表。
- 查询效率稳定:由于任何查询都必须从根节点走到叶子节点,所以查询的效率非常稳定,基本都等于树的高度。
B+树查询过程:
假设我们要在 id
列上查找值为 50
的记录。
- 从根节点开始,将
50
与根节点中的键值比较,确定下一步应该进入哪个子节点。 - 逐层向下,直到到达叶子节点。
- 在叶子节点中,通过二分查找等方式快速定位到
50
,然后根据叶子节点中存储的指针(或直接存储的数据行),找到完整的数据记录。
为什么B+树适合做数据库索引?
- 减少I/O次数:树的高度低意味着磁盘I/O次数少(数据库与磁盘的交互是按页进行的,每个节点通常就是一个页)。这是B+树最大的优势。
- 范围查询效率高 :由于叶子节点是有序链表,当进行范围查询(如
WHERE id > 50
)时,只需找到第一个符合条件的节点,然后沿着链表向后遍历即可,非常高效。 - 查询稳定:每次查询的路径长度(树高)都差不多,性能稳定。
和其他数据结构对比:
- 哈希索引:像Java的HashMap,通过哈希函数定位。优点是等值查询极快(O(1)),缺点是不支持范围查询和排序。InnoDB有一个自适应哈希索引,是系统自动优化的,用户不能手动创建。
- 二叉树:在极端情况下会退化成链表,导致查询效率不稳定(O(n)),不适合。
- 红黑树:虽然能保持平衡,但仍是二叉树,树高较高,I/O次数多,不适合磁盘存储。
4. 索引的类型
根据不同的维度,索引可以分为多种类型。
按数据结构/功能分类:
-
主键索引
- 特点 :一种特殊的唯一索引,不允许有空值(
NULL
)。一张表只能有一个主键索引。 - 作用:用于唯一标识表中的每一行数据。在InnoDB存储引擎中,主键索引也被称为"聚簇索引",表数据文件本身就是按主键索引的顺序组织的(即叶子节点直接包含了完整的数据行)。
- 创建 :
PRIMARY KEY (id)
- 特点 :一种特殊的唯一索引,不允许有空值(
-
唯一索引
- 特点:索引列的值必须唯一,但允许有空值(可以有多个空值)。一张表可以有多个唯一索引。
- 作用:确保某列的数据不会重复,同时提高查询速度。
- 创建 :
UNIQUE INDEX idx_email ON user(email)
-
普通索引
- 特点:最基本的索引类型,没有任何限制(不要求唯一,也不要求非空)。
- 作用:纯粹为了提高查询速度。
- 创建 :
INDEX idx_name ON user(name)
-
组合索引 / 复合索引
- 特点:在多个列上创建一个索引。
- 作用:用于多个列经常同时作为查询条件的场景。
- 核心原则:最左前缀原则 。这是使用组合索引最重要的原则。
- 例如,你创建了
INDEX idx_name_age ON user(name, age)
。 - 这个索引可以用于以下查询:
WHERE name = '张三'
(使用了索引的第一列)WHERE name = '张三' AND age = 25
(使用了索引的全部列)WHERE name = '张三' AND age > 25
(使用了索引的全部列)
- 但不能 (或无法有效使用)用于以下查询:
WHERE age = 25
(没有使用索引的最左列name
,索引失效)WHERE age = 25 AND name = '张三'
(优化器可能会调整顺序,但最好按索引顺序写)
- 例如,你创建了
- 创建 :
INDEX idx_name_age ON user(name, age)
-
全文索引
- 特点 :专门用于在文本内容(如文章、评论)中进行关键词搜索。它使用分词技术,而不是简单的
=
或LIKE
比较。 - 作用 :替代效率低下的
LIKE '%keyword%'
查询。 - 创建 :
FULLTEXT INDEX idx_content ON article(content)
- 特点 :专门用于在文本内容(如文章、评论)中进行关键词搜索。它使用分词技术,而不是简单的
按物理实现分类(主要针对InnoDB):
-
聚簇索引
- 定义 :数据行的物理存储顺序与索引的逻辑顺序相同。索引即数据,数据即索引。
- 特点 :
- 一张表只有一个聚簇索引。
- 通常是主键索引。如果表没有定义主键,InnoDB会选择第一个唯一非空索引作为聚簇索引。如果都没有,InnoDB会内部生成一个隐藏的6字节row_id作为聚簇索引。
- 叶子节点直接存储了完整的数据行。
- 优点:对于主键的排序和范围查询速度极快。
-
非聚簇索引 / 二级索引
- 定义:索引的逻辑顺序与数据行的物理存储顺序不同。
- 特点 :
- 一张表可以有多个非聚簇索引。
- 除了聚簇索引外的其他索引都是非聚簇索引。
- 叶子节点存储的是 主键的值,而不是完整的数据行。
- 回表 :当通过非聚簇索引查询数据时,如果需要访问的列不在该索引中,数据库会先在非聚簇索引的叶子节点中找到主键值,然后再拿着这个主键值去聚簇索引中查找完整的数据行。这个过程叫做 回表。回表是一次额外的I/O操作,会影响性能。
5. 索引的设计原则与实践
创建索引是一门艺术,不是越多越好。
什么情况下应该创建索引?
- 主键和外键列 :主键默认创建索引,外键列经常用于
JOIN
,应该创建索引。 - 经常作为查询条件的列 :在
WHERE
子句中频繁使用的列。 - 经常需要排序的列 :在
ORDER BY
子句中频繁使用的列。 - 经常需要分组的列 :在
GROUP BY
子句中频繁使用的列。
什么情况下不应该创建索引?
- 数据量小的表:对于小表,全表扫描可能比走索引更快,因为索引查找本身也有开销。
- 更新非常频繁的表:写操作多的表,索引的维护成本会很高。
- 区分度低的列:列中只有很少的几个不同值(例如,性别列,只有'男'、'女'、'未知')。为这种列创建索引效果很差,数据库可能认为全表扫描更高效。一个经验法则是,列的唯一值数量超过总行数的20%时,索引效果才比较好。
导致索引失效的常见场景
即使创建了索引,如果SQL语句写得不规范,索引也可能失效,导致全表扫描。
-
在索引列上进行计算或函数操作:
WHERE SUBSTR(name, 1, 3) = '张'
(索引失效)WHERE id + 1 = 10
(索引失效)- 正确写法:将计算和函数移到等号右边,或者改写成可以应用索引的形式。
-
使用
!=
或<>
操作符:WHERE status != 1
(通常会导致索引失效)
-
**使用
IS NULL
或IS NOT NULL
:WHERE name IS NULL
(索引可能失效,取决于数据库和版本)
-
使用
LIKE
以通配符开头:WHERE name LIKE '%三'
(索引失效)WHERE name LIKE '张%'
(索引有效,这是范围查询)- 解决模糊查询问题:使用全文索引或专门的搜索引擎(如Elasticsearch)。
-
类型转换:
- 如果
id
是整型,WHERE id = '123'
(字符串和数字比较,会发生隐式类型转换,可能导致索引失效)。 - 正确写法 :保证比较的类型一致,
WHERE id = 123
。
- 如果
-
违反最左前缀原则:
- 对于组合索引
(a, b, c)
,查询条件中没有a
,则索引失效。
- 对于组合索引
-
使用
OR
连接条件:WHERE name = '张三' OR age = 25
(如果name
和age
上没有单独的索引,或者OR
连接的列不是同一个索引,则索引失效)。- 解决 方案 :使用
UNION ALL
替代OR
。
总结
特性 | 描述 |
---|---|
本质 | 一种用于快速查询的数据结构,牺牲空间和写性能换取读性能。 |
核心原理 | 主要使用 B+树,通过降低树高来减少磁盘I/O次数,实现高效查询。 |
主要类型 | 主键索引 (聚簇)、唯一索引 、普通索引 、组合索引 (最左前缀原则)、全文索引。 |
关键概念 | 聚簇索引 (数据即索引)、非聚簇索引 (需回表)、回表(二次查询)。 |
设计原则 | 在查询频繁、区分度高、更新少的列上创建索引。避免在小表、更新频繁的表上建索引。 |
索引失效 | 避免在索引列上计算、使用 != 、LIKE '%xx' 、OR 等导致索引失效的操作。 |
掌握索引,就掌握了数据库性能优化的钥匙。它要求开发者不仅要懂SQL,更要理解数据库的内部工作原理。