10-MySQL索引
💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!
👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!
1. 索引基础认知
1.1 索引的作用与代价
索引是 MySQL 中提升查询性能的核心工具,无需额外硬件或代码修改,仅通过create index
操作就能将海量数据的查询速度提升成百上千倍。
但索引并非无代价,它会以插入、更新、删除操作的速度为代价 —— 这些写操作需要额外维护索引结构,增加大量 IO 开销。
因此,索引的核心价值在于优化海量数据的检索效率。
1.2 常见索引类型
- 主键索引(primary key)
- 唯一索引(unique)
- 普通索引(index)
- 全文索引(fulltext)
2. 索引的底层原理
2.1 磁盘与 MySQL 的 IO 交互
MySQL 的数据最终存储在磁盘中,理解磁盘结构和 MySQL 的 IO 逻辑,是掌握索引原理的前提。
2.1.1 磁盘的基本结构
- 扇区:磁盘硬件的最小读写单位,默认大小为 512 字节(部分新磁盘为 4096 字节,称为4K扇区)。可以理解为磁盘上的 “小格子”,每个 “格子” 存 512 字节数据。
- 磁道与柱面:磁盘盘片表面有多个同心圆,每个同心圆称为 “磁道”;多盘磁盘中,所有盘片相同半径的磁道共同构成 “柱面”。
- 磁头:每个盘片的正反面各有一个磁头,用于读取 / 写入磁道上的数据。
2.1.2 MySQL 的 IO 单位
MySQL(InnoDB 引擎)不直接按扇区读写数据,而是以16KB 的 Page(页) 为 IO 基本单位。
磁盘是靠机械部件转动、移动来读写数据的,读写速度远慢于内存——内存读写像从桌上拿东西,磁盘读写像从仓库找东西——因此 IO 效率的关键是 “减少 IO 次数” ,而非 “单次 IO 数据量”。
MySQL 把磁盘里的数据按 16KB 大小分成 “页(Page)”,读取数据时一次性将 1 个 Page(16KB)加载到内存中的缓存池(Buffer Pool),后续若需访问该 Page 中的其他数据,直接从内存读取,无需再次访问磁盘。
示例:若要查询表中 10 条数据,若这 10 条数据在同一个 Page 中,仅需 1 次磁盘 IO(加载整个 Page 到内存);若分散在 10 个 Page 中,则需 10 次磁盘 IO,效率差距极大。
2.2 索引的底层数据结构:B + 树
面试高频考点 —— 为何 MySQL 选择 B + 树作为索引底层结构,而非其他数据结构?
2.2.1 其他数据结构的缺陷
链表 | 数据按顺序存储,查询需从表头线性遍历 |
二叉搜索树 | 左子树值 < 根节点 < 右子树值。但有序插入数据时(如按 1、2、3、4 顺序插入),会退化为 “链表”,查询效率骤降。 |
AVL树/红黑树 | 虽然AVL 严格平衡、红黑树近似平衡,但本质是 “二叉树”,树的高度较高,每层对应 1 次磁盘 IO。 |
Hash表 | 通过哈希函数将键值映射到内存地址,查询单值数据快,但不支持范围查询,且 InnoDB、MyISAM 引擎不支持 Hash 索引。 |
B树 | 单个节点能存储的键值数量少,导致树的高度较高;且叶子节点不连续,无法高效支持范围查询。 |
2.2.2 B + 树的优势(InnoDB/MyISAM 均采用)
B + 树是 “多阶平衡树”(每个节点可存储多个键值,远超 2 个),核心优势如下:
-
非叶子节点仅存键值,不存数据,使得单个节点能存储更多键值,树的高度大幅降低,从而减少磁盘 IO 次数,效率极高。
-
所有实际数据仅存于叶子节点,且叶子节点通过 “双向链表” 连接,这使得范围查询效率极高。
-
InnoDB 会自动按主键对数据排序(即使插入时主键无序,也会调整为有序存储),这是 B + 树能高效工作的前提 —— 保证键值有序,才能通过目录页快速定位目标 Page。
2.3 聚簇索引与非聚簇索引
不同存储引擎的索引实现方式不同,这是面试常考点,需重点区分:
2.3.1 聚簇索引(InnoDB 特有)
- 核心特征:聚簇索引的叶子节点(最底层)直接存储完整的数据记录,而非数据地址。InnoDB 中,主键索引 = 聚簇索引—— 若表未指定主键,InnoDB 会自动选择 “非空且值唯一的索引” 作为聚簇索引;若“非空且值唯一的索引”也没有,则生成隐藏的自增主键作为聚簇索引。
InnoDB 表的索引和数据存储在同一个文件中,文件后缀为.ibd
(InnoDB Data File)。建表后,.ibd
文件非空,即使无数据也包含主键索引结构。
- 辅助索引(普通索引 / 唯一索引)的逻辑:回表查询。InnoDB 的非主键索引称为 “辅助索引”,其叶子节点仅存储 “主键值”,而非完整数据。
回表查询时分两步:
- 通过辅助索引找到对应的主键值(如通过
name='张三'
找到主键id=123
); - 再通过主键索引(聚簇索引)找到完整的数据记录(如通过
id=123
找到name='张三'
、age=25
等完整信息)。
2.3.2 非聚簇索引(MyISAM 特有)
- 核心特征:非聚簇索引的叶子节点仅存储 “数据记录的磁盘地址”,而非完整数据。MyISAM 中,所有索引(包括主键索引)都是非聚簇索引,主键索引仅多了 “字段值唯一” 的约束。
MyISAM 表的索引和数据分开存储:
- 索引文件:后缀为
.MYI
(MyISAM Index File); - 数据文件:后缀为
.MYD
(MyISAM Data File)。建表后,无数据时.MYD文件
为空
- 辅助索引的逻辑:无需回表。MyISAM 的普通索引与主键索引结构完全一致,仅键值不同(主键索引键值唯一,普通索引键值可重复)。查询时直接通过索引找到数据地址,再根据地址读取数据,无需回表。
3. 索引的实操语法(必掌握)
3.1 主键索引
- 字段值非空且唯一
- 一个表只能有一个主键索引(可由多列组成复合主键)
- 主键索引的列一般是数字类型
3.1.1 建表时直接指定主键(推荐)
-- 方式1:字段后直接加primary key
create table user1(id int primary key, -- id为主键,非空且唯一name varchar(30) not null, age int
);-- 方式2:表末尾指定主键(支持复合主键)
create table user2(id int, name varchar(30),age int,primary key(id) -- 单字段主键-- primary key(id, name) -- 复合主键
);
3.1.2 建表后添加主键
create table user3(id int not null, -- (1)必须非空name varchar(30) not null,age int
);-- (2)给id字段添加主键前需确保id无重复值
alter table user3 add primary key(id);-- 若相应字段有重复值或允许空,添加会失败,需先删除重复值/设置非空
3.2 唯一索引
- 字段值唯一(允许 NULL,若指定 NOT NULL 则等价于主键索引)
- 一个唯一键可由多个列同时承担
- 一个表可创建多个唯一索引
3.2.1 建表时指定唯一索引
-- 方式1:字段后加unique
create table user4(id int primary key, phone varchar(11) unique not null, -- 手机号唯一且非空email varchar(50) unique -- 邮箱唯一,允许空
);-- 方式2:表末尾指定unique
create table user5(id int primary key,phone varchar(11) not null,email varchar(50),unique(phone),unique(email)
);
3.2.2 建表后添加唯一索引
create table user6(id int primary key,phone varchar(11) not null,email varchar(50)
);-- 添加唯一索引前必须保证该列值无重复
alter table user6 add unique(phone);
3.3 普通索引(较常用)
- 创建普通索引的列,其列值可为NULL,也可重复
- 一个普通索引可由多个列同时承担
- 一个表可有多个普通索引
3.3.1 建表时指定普通索引
create table user8(id int primary key,name varchar(20) not null,email varchar(30),category_id int, -- 分类ID(频繁作为查询条件)index(name)
);
3.3.2 建表后添加普通索引
方式1:
create table user9(id int primary key,name varchar(20) not null,email varchar(30),category_id int
);alter table user9 add index(name);
方式2:用create index(推荐,更灵活,可指定索引名)
create table user10(id int primary key,name varchar(20) not null,email varchar(30),category_id int
);-- create index 索引名 on 表名(列名);
create index idx_category on user10(category_id);
3.4 全文索引
- 用于大量文字字段的检索
- MyISAM存储引擎支持全文索引,InnoDB存储引擎5.6以后才支持全文索引,传统实现用MyISAM
- 默认仅支持英文检索,不支持中文
3.4.1 创建全文索引
create table article(id int unsigned auto_increment not null primary key, -- 自增主键title varchar(200) not null, body text not null, -- 文章内容(大量文字)fulltext(title, body)
)engine=MyISAM; -- 必须指定MyISAM引擎insert into article(title, body) values
('MySQL Tutorial', 'DBMS stands for DataBase Management System. MySQL is a popular DBMS.'),
('How To Use MySQL Well', 'After you went through a MySQL tutorial, you can use MySQL well.'),
('Optimizing MySQL', 'In this tutorial we will show how to optimize MySQL queries.'),
('MySQL vs. YourSQL', 'In the following database comparison, we compare MySQL and YourSQL.'),
('MySQL Security', 'When configured properly, MySQL is a secure database system.');
3.4.2 正确使用全文索引
- 在指定表的指定字段中高效搜索包含某指定关键词的所有记录,并返回这些记录的完整信息——如,在
articles
表的title
和body
字段中,高效搜索包含关键词database
的所有记录,并返回这些记录的完整信息。
如果用 select * from article where title like '%database%' or body like '%database%'; 查询,虽能查询结果,
但不会触发全文索引,会执行全表扫描(逐行扫描整个表),效率低。
用explain验证是否使用索引:
explain select * from article where title like '%database%' or body like '%database%'\G;
type: ALL
:表示全表扫描key: NULL
表示未使用任何索引
若表已对 title
和 body
创建全文索引,应该用 match(列名1,...) against('关键词') :
select * from article where match(title, body) against('database');
(了解即可)MySQL 全文索引的 “噪声词” 机制:若(包含关键词的记录数)/(所有记录总数)> 50%,全文索引不会对其生效,导致查询返回空集Empty set (0.00 sec)。
当表中数据量较少时,容易触发该机制,导致常见词无法被检索到。这里表中总共有 5 条记录,包含
database
(或其变体,如DataBase
)的记录有 3 条,出现比例 3/5 > 50%,那么为正常使用,增加两条不含database的记录。这说明:全文索引适合数据量较大的场景。
explain验证:
- type: fulltext 表示使用了全文索引。
- 对于非主键索引(唯一索引、普通索引、全文索引),若创建索引时未指定索引名,MySQL 会默认使用索引包含的第一个字段名作为索引名——explain 结果中key对应索引名。
3.5 索引查询与删除
3.5.1 查看索引(3 种方式)
方式1:详细信息(推荐)
show keys from 表名\G; -- \G使结果纵向显示,更清晰
方式2:简洁信息
show index from 表名;
方式3:仅看主键和唯一键
desc 表名;
3.5.2 删除索引
- 一个表只有一个主键索引,所以删除主键索引时不用指明索引名;而一个表可能有多个非主键索引,所以删除非主键索引时需指明索引名。
删除主键索引
alter table 表名 drop primary key;
删除唯一索引/普通索引
方式1:
alter table 表名 drop index 索引名;
对于非主键索引(唯一索引、普通索引、全文索引),若创建索引时未指定索引名,MySQL 会默认使用索引包含的第一个字段名作为索引名。
方式2:
drop index 索引名 on 表名;
4. 索引创建原则(面试高频问答)
创建索引并非越多越好,需遵循以下原则,避免无效 / 低效索引:
-
高频查询字段优先创建索引:频繁出现在
where
子句、order by
、group by
中的字段,创建索引能显著提升效率。例如:订单表中频繁执行select * from orders where user_id=123 order by create_time
,给user_id
和create_time
创建索引(或复合索引)。 -
唯一性太差的字段不适合单独创建索引:若字段值重复率高(如 “性别” 只有男 / 女,“状态” 只有 0/1/2),即使频繁查询,创建索引效果也差 —— 因为索引筛选后仍需扫描大量数据。例如:
select * from user where gender='男'
,若表中 80% 是男性,索引无法有效筛选,不如全表扫描。 -
更新频繁的字段不适合作索引:更新字段时,需同步维护索引结构(如 B + 树节点调整),频繁更新会导致大量 IO 开销,抵消索引的查询优势。例如:商品表的 “库存” 字段(每秒更新多次),不适合创建索引;而 “商品 ID”(几乎不更新)适合创建索引。
-
不会出现在查询条件中的字段不创建索引:若字段从未出现在
where
、order by
等子句中,创建索引无意义,仅会浪费存储空间和增加写操作开销。例如:用户表的 “备注” 字段(仅用于存储附加信息,不用于查询),无需创建索引。