索引是存储引擎用于快速查找数据的数据结构。简单来说,如果我们将一张表中的所有内容看作一本书,索引就相当于书的目录。
但也有缺点:
在 MySQL 中,**“索引的数据结构”**指的是数据库底层用来存储索引的数据组织形式。
通常情况下,我们在使用索引时不会去刻意看它的数据结构,因为MySQL 会根据存储引擎自动选择最合适的数据结构,以下是一些常见的存储引擎的默认索引结构:
存储引擎 | 默认索引结构 | 备注 |
---|---|---|
InnoDB | B+ 树 | 主流引擎,支持事务、外键等 |
MyISAM | B+ 树 | 不支持事务 |
Memory | 哈希(默认)或 B+ 树 | 可手动指定类型 |
NDB | 哈希 | 分布式数据库场景 |
[!NOTE]
除上之外,有两种比较特殊的索引类型:
MySQL从 5.6 开始支持
FULLTEXT
索引(全文索引),全文索引的数据结构是倒排索引
;MySQL从 5.7 开始支持
SPATIAL
索引(空间索引),空间索引的数据结构是R-Tree
(矩形树);
简单介绍一下上面出现的几种索引结构:
B树(英语:B-tree),是一种在计算机科学自平衡的树,能够保持数据有序。 这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。 B树,概括来说是一个一般化的二叉搜索树,一个节点可以拥有2个以上的子节点。
B-树有如下特点:
1、所有键值分布在整颗树中(索引值和具体data都在每个节点里);
2、任何一个关键字出现且只出现在一个结点中;
3、搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);
4、在关键字全集内做一次查找,性能逼近二分查找;
概述:B+树是B-树的变体,也是一种多路搜索树
它与 B- 树的不同之处在于:
所有关键字存储在叶子节点,内部节点(非叶子节点)并不存储真正的 data;
为所有叶子结点增加了一个链指针;
B+树索引的优点:
优点 | 详细说明 |
---|---|
支持范围查询 | B+ 树中的数据是按序排列的,非常适合用于 < 、> 、BETWEEN 、LIKE 'abc%' 等范围操作。 |
支持排序 | B+ 树天然有序,可以用于 ORDER BY 的加速,避免额外排序操作。 |
支持联合索引的最左前缀匹配 | 例如 (a, b, c) 的索引可以支持 (a) 、(a, b) 等组合的查询。 |
磁盘友好,IO 次数少 | B+ 树节点包含多个键值指针(高扇出),树的高度较低,因此磁盘访问次数少。 |
叶子节点间有指针串联,便于区间遍历 | 叶子节点之间通过链表连接,便于顺序扫描(如分页、区间扫描)。 |
所有值都存在叶子节点,便于范围遍历 | 非叶子节点仅存索引键,所有数据只存在于叶子节点中,结构简洁高效。 |
B+树索引的缺点:
缺点 | 说明 |
---|---|
构建成本较高 | 建索引时需要构建树结构,占用 CPU 和 IO 资源。 |
维护成本较高 | 数据插入、删除、更新时可能导致树结构的变更(如节点分裂、合并、重排)。 |
对等值查询性能略低于 Hash 索引 | 因为需要从根节点遍历到叶子节点,等值查询的效率不如 Hash 索引的常数级访问。 |
空间利用率略低于 Hash(但仍较高) | 为了维持树的平衡,节点可能存在部分空槽。 |
不适合频繁变动的大型数据集 | 在高频更新、大量插入/删除的情况下,B+ 树可能频繁调整结构,性能受影响。 |
Hash 索引是一种基于哈希表实现的索引结构,它通过对索引列的值进行哈希计算,将结果映射到哈希表中的位置,从而实现快速的查找操作。
Hash 索引通过以下步骤加速数据访问:
Hash 索引的优点:
优点 | 描述 |
---|---|
查询速度快 | 基于哈希算法可以在常数时间内定位数据,效率高于 B+ 树的 O(log n) 查询。 |
精确匹配性能极高 | 特别适合 = 、in() 、is null 这类等值查询。 |
Hash 索引的缺点:
缺点 | 描述 |
---|---|
不支持范围查询 | 不能用于 > 、< 、between 、like 'abc%' 等操作。 |
无法利用索引排序 | Hash 索引中数据无序,因此不能用于 order by 的加速。 |
哈希冲突 | 不同值可能有相同的哈希值,会导致性能下降(需链表/桶解决冲突)。 |
不支持部分匹配 | 对于联合索引,不能使用其中一部分字段进行查询。 |
内存开销大 | 哈希表通常是放在内存中的,过多会占用内存资源。 |
虽然 InnoDB 不直接支持 Hash 索引,但它会自动地在热点查询上创建自适应哈希索引,以提升查询性能:
- 触发条件:某些页上的查询频率足够高;
- 原理:从 B+ 树页中提取等值查询信息,生成一个 Hash 索引结构;
- 管理方式:InnoDB 自动维护,用户无法手动干预;
- 可以通过参数
innodb_adaptive_hash_index
来开启或关闭(默认开启)。
倒排索引(Inverted Index)是一种常用于全文搜索引擎的数据结构,尤其常见于如 Elasticsearch、Lucene、Solr 这样的全文检索系统中。它非常适合用于查找“某个词出现在哪些文档中”的场景。
倒排索引的思想是根据“关键词”来反查包含这个关键词的文档或记录。
倒排索引的结构:
倒排索引的结构通常包括两个核心部分:
举个例子:
假设有如下三个文档:
文档ID | 内容 |
---|---|
1 | I love deep learning |
2 | deep learning is fun |
3 | I love machine learning |
建立倒排索引后结构如下:
关键词 | 倒排列表 |
---|---|
I | [1, 3] |
love | [1, 3] |
deep | [1, 2] |
learning | [1, 2, 3] |
is | [2] |
fun | [2] |
machine | [3] |
当用户搜索关键词 “deep”,系统就可以直接从倒排表中定位到文档 1 和 2,而无需逐个扫描所有文档。
倒排索引的优点:
优点 | 描述 |
---|---|
高效的关键词检索 | 非常适合关键词查找,性能远高于传统索引。 |
支持复杂查询 | 支持多关键词、短语匹配、布尔查询(AND/OR/NOT)等。 |
支持评分和排序 | 可以结合 TF-IDF、BM25 等算法,对相关性打分并排序。 |
倒排索引的缺点:
缺点 | 描述 |
---|---|
占用空间大 | 每个关键词都要维护完整的文档列表,数据量庞大。 |
建立和更新开销大 | 插入/修改文档时需要更新多个关键词的倒排列表。 |
不适合范围查询 | 无法高效支持 < 、> 这类数值范围过滤。 |
倒排索引常用于搜索引擎、站内全文搜索、日志检索、文本推荐、内容相关性分析等场景。
R 树是用于空间访问方法的树形数据结构,即用于索引多维信息,例如地理坐标、矩形或多边形。
R 树是一种 平衡树结构,它将空间对象(如点、线、矩形等)按其**最小边界矩形(MBR:Minimum Bounding Rectangle)**组织起来,使得可以快速进行空间搜索。
R-Tree 的结构特点:
M
,超过就分裂;以下是一个二维矩形的 R 树的简单示例:
再举一个简单的例子:
假设有如下空间对象:
R-Tree 会将这些矩形打包为一个或多个 MBR,例如:
这样可以在查询时快速排除与目标不相交的区域,减少访问次数。
R-Tree 的优点:
优点 | 描述 |
---|---|
高效的空间查询 | 快速支持“交集”、“包含”、“相邻”等空间操作 |
适合多维数据 | 可以处理 2D、3D、甚至更高维度的空间对象 |
支持动态插入与删除 | 灵活性强,不需重建索引 |
结构平衡 | 插入和删除后结构自动调整,性能稳定 |
R-Tree 的缺点:
缺点 | 描述 |
---|---|
空间重叠问题 | 不同节点的 MBR 可能存在大量重叠,导致查询时多路径遍历 |
不适合频繁更新 | 大量插入和删除可能导致频繁分裂或合并,性能下降 |
不适合高维数据 | 高维(通常指 10 维以上)下“维度灾难”显著,效果不如专门的高维索引(如 KD-Tree、LSH) |
简单来说:
R-Tree 是面向空间数据的“B+ 树”,用最小矩形组织多维对象,支持高效的地理与空间查询,是地图与图形领域的关键索引结构。
上文索引的数据结构也算一种按照数据结构维度划分的分类方式了,接下来我们按照其他的方式进行划分:
按照对索引的存储方式来对索引进行划分,可以分为聚簇索引和非聚簇索引。
聚簇索引(Clustered Index):索引结构和数据本身是一起存放的。
一个表只能有一个聚簇索引,因为数据只能有一种物理排列方式。
数据库中的物理排列方式指的是:
- 数据在磁盘上的实际存储顺序。
- 聚簇索引会按照某个字段(通常是主键)的顺序,把整行数据顺着索引排好并写入磁盘。聚簇索引要求数据必须跟着这个索引字段的顺序存储。
InnoDB 中的主键索引就属于最常用的聚簇索引。在主键索引中,主键索引本身的值中就包含了整行数据,我们通过主键查询一次便可以获得需要的数据。
聚簇索引的优点:
聚簇索引的缺点:
[!NOTE]
**在InnoDB中,聚簇索引默认是主键。**如果表中没有主键,InnoDB会选择一个唯一的非空索引来作为聚簇索引,如果也没有,则会隐式定义一个主键来作为聚簇索引。InnoDB引擎不允许手动指定非主键列为聚簇索引,但可以通过不设置主键的方式让一个唯一的非空索引来作为聚簇索引。
在 MyISAM / Memory 中没有聚簇索引的概念,统一都是非聚簇索引。
而在SQL Server (使用的是自定义的存储引擎)中,可以手动将非主键列设置为聚簇索引:
CREATE CLUSTERED INDEX idx_age ON users(age);
这就会让
age
字段作为聚簇索引,而主键索引则会自动退化成非聚簇唯一索引。
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引。
显然,除了聚簇索引外,其他所有的索引通常都可以视为非聚簇索引。
非聚簇索引的一些特点:
非聚簇索引的优点:
非聚簇索引的缺点:
[!NOTE]
在MySQL 的 MyISAM 引擎中,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分,索引可以分为:
主键索引、普通索引、唯一索引、覆盖索引、联合索引、全文索引、前缀索引、空间索引。
主键索引是数据库为主键列自动创建的唯一索引,它用于唯一标识表中的每一行数据。
一张数据表最多只能有一个主键,且主键不能为空,不能重复。
在InnoDB存储引擎的表中,如果没有指定表的主键,InnoDB会自动选择一个不能为空的唯一索引对应的字段作为主键,如果没有则会自动创建一个6Byte 的自增主键(隐式定义)。
创建主键索引示例:
CREATE TABLE users (
id INT PRIMARY KEY, -- 主键自动带索引
username VARCHAR(100),
email VARCHAR(100)
);
或:
CREATE TABLE users (
id INT,
username VARCHAR(100),
PRIMARY KEY (id) -- 另一种主键定义方式
);
创建表后添加主键:
ALTER TABLE users
ADD CONSTRAINT pk_users_id PRIMARY KEY (id); -- CONSTRAINT pk_users_id 为主键约束名可不写
主键索引的范围和聚簇索引的很像,实际来看,主键索引更强调是一种约束性索引,聚簇索引更强调是一种物理存储结构,两者属于不同的划分方向。
在 InnoDB 中,主键索引和聚簇索引的实际表现完全一致。
而在SQL Server中,就如我们在聚簇索引中所说,会出现主键索引不是聚簇索引的情况,二者不像InnoDB中那样强绑定。
二级索引是相对于主键索引而言的其他所有索引,通常用于加速非主键列的查询。
二级索引(Secondary Index)的叶子节点存储的数据是主键的值或行地址,也就是说,通过二级索引可以定位主键的位置,二级索引又称为辅助索引/非主键索引。
二级索引的范围和非聚簇索引很像,实际来看与之前相似,二级索引强调主键索引以外的所有索引,通常用于加速非主键列的查询。非聚簇索引强调索引和数据存储分离,通常具有独立的存储结构。
因为在InnoDB存储引擎中,主键索引和聚簇索引的实际表现完全一致,所以二级索引的实际表现和非聚簇索引也是一致的。
普通索引,唯一索引,前缀索引等索引都属于二级索引。
普通索引(Normal Index) 是数据库中最基础、最常见的一种索引类型。它不限制字段值的唯一性,只是为了加快数据检索速度。
普通索引的作用:
普通索引的创建方式:
创建表时添加普通索引:
CREATE TABLE users (
id INT,
name VARCHAR(100),
INDEX idx_users_name (name) -- 普通索引
);
在已有表上添加普通索引:
CREATE INDEX idx_users_name ON users (name);
或使用 ALTER TABLE:
ALTER TABLE users ADD INDEX idx_users_name (name);
唯一索引是一种数据库索引,它保证索引列中的所有值是 唯一的,即不允许有重复值。
在定义唯一索引的列中,插入或更新数据时,数据库会检查该列(或列组合)是否已存在相同值,若存在则会报错,防止重复。
与主键不同,唯一索引允许列中存在多个空值(NULL)且一个表中可以定义多个唯一索引。
唯一索引的作用:
唯一索引的创建方式:
定义表时直接定义:
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE -- 定义唯一索引
);
使用 CREATE UNIQUE INDEX
显式创建:
CREATE UNIQUE INDEX idx_users_email ON users (email); -- idx_users_email为名称可以不写
使用 ALTER TABLE
添加唯一约束:
ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);
前缀索引是针对字符串类型字段(如 CHAR
、VARCHAR
、TEXT
)的一种特殊索引方式,它不是索引整个字段的内容,而是只索引该字段的前几个字符。
如果我们不需要整个字符串来区分不同记录,仅用前几位就足够区分,那么就可以使用前缀索引节省空间。
前缀索引的作用:
LIKE 'abc%'
可以利用前缀索引前缀索引的创建方式:
CREATE INDEX idx_name_prefix ON users(name(10));
含义:只对 name
字段的前 10 个字符建立索引。
全文索引是一种针对大文本字段(如 TEXT
、VARCHAR
)设计的索引类型,用于在海量文本中高效查找关键词或短语。
它不再基于“字面值对比”,而是对文本内容进行“分词(tokenization)+ 倒排索引”的处理,这使全文索引的效率远超模糊查询。
全文索引适用于文章搜索、产品描述、评论系统、日志数据查询等。
在 MySQL 中,全文索引通常用于以下字段类型:
CHAR
VARCHAR
TEXT
(如 TINYTEXT
, TEXT
, MEDIUMTEXT
, LONGTEXT
)Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持全文索引。
全文索引的局限:
ngram
插件或使用第三方全文引擎(如 Elasticsearch)=
比较、范围查询,只能用于 MATCH ... AGAINST
查询全文索引的创建方式:
创建表时添加全文索引:
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(255),
content TEXT,
FULLTEXT(title, content)
);
已有表上添加全文索引:
ALTER TABLE articles ADD FULLTEXT(title, content);
CREATE FULLTEXT INDEX idx_title_content ON articles(title, content);
全文索引的使用方式:
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('数据库 索引');
支持布尔模式:
SELECT * FROM articles
WHERE MATCH(title) AGAINST('+数据库 -缓存' IN BOOLEAN MODE);
以上代码的含义是:必须包含 “数据库” 且 不能包含 “索引”
如果某条 SQL 查询中涉及的所有字段(包括 WHERE
、SELECT
、ORDER BY
中使用的字段)都被某个索引包含,那么该索引就被称为“覆盖索引”。换句话说:查询只访问索引,不需要回表查数据行。
覆盖索引不是单独的索引类型,它是一种查询优化行为,通常由 联合索引实现。
举个例子:
假设有如下表结构:
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100), age INT );
创建索引:
CREATE INDEX idx_name_email ON users(name, email);
查询语句:
SELECT email FROM users WHERE name = 'Alice';
这个查询:
- 使用了
WHERE name = ...
- 查询了
- 这两个字段都包含在索引
idx_name_email
中所以这个查询是 覆盖索引查询,无需访问数据表,只读索引就能返回结果。
覆盖索引的优点:
覆盖索引的不足:
SELECT
中字段的顺序无所谓,只要这些字段都包含在索引里,就能避免回表。但是如果还要让查询命中索引做过滤或排序,WHERE
和 ORDER BY
中字段的顺序必须符合最左前缀匹配原则。最左前缀匹配原则请看下节。
联合索引(Composite Index) 是指一个索引包含多个列(字段),它能够加速基于这些列的查询。联合索引与单列索引相比,可以在多个列的组合查询中提供更高的性能优化。
联合索引的创建方式:
CREATE INDEX idx_name_age_email ON users(name, age, email);
联合索引的应用场景:
多条件查询:当查询条件涉及多个列时,使用联合索引可以大幅提高查询性能。例如:
SELECT * FROM users WHERE name = 'Alice' AND age = 25;
排序:如果 ORDER BY
子句涉及多个列,联合索引也能提高排序性能:
SELECT * FROM users WHERE age > 30 ORDER BY name, age;
**避免回表:**如果联合索引包含了查询所需的所有列,就可以避免回表查询(直接从索引中读取数据),提高查询效率。
最左前缀匹配原则指的是:对于一个联合索引,只有当查询条件中的列顺序与索引中列的顺序从左到右完全匹配时,索引才会被充分利用。
关键点:
示例:
建立以下表和联合索引:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
age INT,
email VARCHAR(100)
);
CREATE INDEX idx_name_age_email ON users(name, age, email);
联合索引 idx_name_age_email
包含 name
、age
和 email
三列,查询条件必须遵循最左前缀原则才能完全匹配该索引。
完全匹配索引:
SELECT * FROM users WHERE name = 'Alice' AND age = 25 AND email = '[email protected]';
这个查询条件完全匹配 idx_name_age_email
索引中的三个列,由于条件涉及了索引中的所有列,索引会被完全命中,查询会非常高效。
SELECT * FROM users WHERE name = 'Alice' AND age = 25;
这个查询使用了联合索引中的前两个列 name
和 age
,因为查询条件包含了联合索引的最左边两列,所以该查询可以完全使用索引。
跳过最左边的列:
SELECT * FROM users WHERE age = 25 AND email = '[email protected]';
这个查询没有使用最左边的列 name
,而是直接跳过,根据最左前缀匹配原则,联合索引的前缀必须与查询条件一致。因此,idx_name_age_email
这个联合索引无法用于该查询。
只使用索引的最左列:
SELECT * FROM users WHERE name = 'Alice';
这个查询只包含了 name
,它正好匹配了 idx_name_age_email
索引的最左列 name
。该查询可以利用索引。
SELECT * FROM users WHERE name = 'Alice' AND email = '[email protected]';
这个查询中name
字段会使用索引,然后对结果进行 email = '[email protected]'
的过滤。
范围查询: 当查询条件包含范围查询(如 >
, <
等)时,最左前缀匹配的规则会有所不同。范围查询的列只能是联合索引的最右边的一列,后面的列无法用于索引匹配。但对于 >=
、<=
、BETWEEN
、like
前缀匹配的范围查询,并不会停止匹配。
SELECT * FROM users WHERE name = 'Alice' AND age > 25 AND email = '[email protected]';
因为 age > 25
是范围查询,数据库只能利用 name
和 age
来优化查询,而无法利用 email
列进行进一步优化。
MySQL 8.0.13 版本引入了索引跳跃扫描(Index Skip Scan,简称 ISS),索引跳跃扫描允许数据库在没有提供联合索引最左列查询条件的情况下,依然利用该联合索引进行查询,通过“遍历前导列的不同值”,实现跳跃式访问。简单来说,就是可以处理上面 情况3 出现的现象,但MySQL中的索引跳跃扫描需要遍历多个前导列值,性能可能比普通的全表扫描还差,尤其是当前导列的基数很高时,所以基本上用不上。
空间索引(Spatial Index)是一种用于加速 空间数据(如地理位置、几何图形)查询 的专用索引结构。它不同于传统的 B-Tree 索引,通常用于支持如地图、地理信息系统(GIS)、LBS(基于位置的服务)等场景中的 范围查询、邻近查询、交集判断等空间操作。
MySQL 支持空间数据和空间索引,主要通过:
GEOMETRY
(几何) 类型(父类型)POINT
(点), LINESTRING
(线), POLYGON
(二维平面的多边形) 等MyISAM 引擎支持真正的空间索引(使用 R-Tree),InnoDB 自 MySQL 5.7 开始支持空间索引(但使用的是 B-Tree 变体)
MySQL创建空间索引:
CREATE TABLE places (
id INT PRIMARY KEY,
location POINT NOT NULL,
SPATIAL INDEX(location) -- 创建空间索引
);
空间查询常见操作:
配合 ST_
系列函数使用:
函数 | 说明 |
---|---|
ST_Within(a, b) |
判断 a 是否在 b 内部 |
ST_Intersects(a, b) |
判断 a 与 b 是否相交 |
ST_Distance(a, b) |
计算两对象的距离 |
MBRContains() / MBRWithin() |
使用最小边界矩形加速判断 |
示例:
-- 查询在某区域内的点
SELECT * FROM places
WHERE ST_Within(location, ST_GeomFromText('POLYGON((...))'));
空间索引的应用场景:
MySQL创建空间索引:
CREATE TABLE places (
id INT PRIMARY KEY,
location POINT NOT NULL,
SPATIAL INDEX(location) -- 创建空间索引
);
空间查询常见操作:
配合 ST_
系列函数使用:
函数 | 说明 |
---|---|
ST_Within(a, b) |
判断 a 是否在 b 内部 |
ST_Intersects(a, b) |
判断 a 与 b 是否相交 |
ST_Distance(a, b) |
计算两对象的距离 |
MBRContains() / MBRWithin() |
使用最小边界矩形加速判断 |
示例:
-- 查询在某区域内的点
SELECT * FROM places
WHERE ST_Within(location, ST_GeomFromText('POLYGON((...))'));
空间索引的应用场景: