8.数据库索引
一.什么是索引?
索引是一种数据结构,可以快速定位到数据的位置,用来加快查询速度。类似一本书的目录
原理:索引基于数据表中的一列或多列创建。存储这些列的值以及指向实际数据记录的位置
用额外的存储空间和一定的维护成本,换取查询速度的巨大提升
二.索引的分类
1.按数据结构分类
1)B树索引
2)B+树索引
B+树是MySQL中最常用的索引结构,尤其是在InnoDB存储引擎中,几乎所有的索引都是B+树索引。
B+树是一种自平衡的树形数据结构,所有数据都存储在叶子节点,且叶子节点通过链表连接,提供顺序访问;非叶子节点只存储键值和指向子节点的指针
特点:
- 支持高效的等值查询、范围查询、排序
- 查询效率高,查询、插入和删除操作的时间复杂度都是O(log N)
- 每个节点存储多个键值,树的高度始终保持一个较低的水平,减少了磁盘I/O次数,适用于大数据量场景
3)哈希索引
MySQL中,只有Memory引擎显式支持哈希索引;InnoDB不支持哈希索引,但它会自动创建哈希索引,称为自适应哈希索引(索引的索引)
哈希索引基于哈希表实现,通过计算键值的哈希值来查询数据
特点:
- 等值查询快,可以在O(1)时间内匹配
- 只能用于等值查询,不支持范围查询
- 大量重复键值的情况下,哈希冲突会影响性能
4)全文索引
主要用于MyISAM和InnoDB存储引擎
全文索引用于文本数据的搜索,通常用于对长文本列进行全文索引
特点:
- 适合处理文本字段中的关键字查找,支持单词匹配、短语匹配、模糊匹配等复杂文本搜索
- 只适用于文本类型字段,不能用于数值或日期等其他数据类型
5)位图索引
为索引列的每一个唯一值创建一个位图。位图中的每一位代表表中的一行,如果某行具有该值,对应为置为1,否则为0
特点:
- 适用于取值有限且变化不频繁的字段,如性别、状态等
- 多个位图索引可以通过位运算组合查询
6)空间索引
MyISAM和InnoDB支持空间数据类型的索引
空间索引用于存储和查询空间数据,如地理坐标、地图数据等
特点:
- 仅限于存储空间数据类型的字段,穿件和维护复杂度较高
2.按物理存储分类
1)聚集索引
2)非聚集索引
详情见3.聚簇索引和非聚簇索引的区别?-CSDN博客
3.按字段特性分类
1)唯一索引
索引键的值在整个表中必须是唯一的,不允许重复值,允许NULL值
特点:
- 保证数据的唯一性约束
- 主键约束primary key会自动创建一个唯一索引
- 唯一约束unique constraint会自动创建一个唯一索引
2)主键索引
特殊的唯一索引,用于唯一标识表中的每一行数据
特点:
- 不允许NULL值
- 一个表有且仅有一个主键
3)普通索引
最基本的索引类型,没有任何唯一性约束,也称为非唯一索引
特点:
- 主要目的就是加速查询,允许索引键的值重复出现
4)函数索引
索引键不是列本身,而是基于一个或多个列应用函数或表达式计算的结果
特点:
- 加速在where子句或join条件中使用了函数或表达式的查询
5)前缀索引
只对列值的前面一部分字符或字节创建索引,而不是整个列
特点:
- 减少索引占用的存储空间
- 会降低索引的区分度,可能返回更多行
4.按字段个数分类
1)单列索引
基于表中单个列创建的索引
2)联合索引
基于表中多个列创建的索引
特点:
- 遵循最左前缀原则
- 如果联合索引的索引值已经满足了查询条件,就不会进行回表,直接返回结果
- 相比单列索引,联合索引能更高效地组合多条件查询
3)覆盖索引
覆盖索引是指一个索引包含了查询语句中所有需要返回的字段,使数据库引擎可以直接从索引中获取所需数据,无需回表查询数据行
三.索引的优点和缺点
优点:
- 加快数据检索,降低数据库I/O成本
- 增强数据完整性:唯一索引确保列值无重复,主键索引强制非空,防止数据冗余
- 优化排序和分组:索引本身按键值有序存储,直接支持排序和分组操作,减少查询中排序和分组的时间
- 加速表连接:外键字段的索引可以高效关联多表数据
缺点:
- 占用存储空间
- 降低写操作性能:增删改数据需要同步维护索引,增加I/O开销。频繁更新的表性能可能下降
- 维护成本高
四.使用索引一定能提升效率吗
使用索引不一定能提升效率,甚至可能降低性能
1.不适合创建索引的场景
1)低频查询字段
字段不常查询,索引的维护成本远高于查询收益
2)低区分度字段
数据重复率极高,索引过滤效率低,回表成本可能高于全表扫描
3)小数据量表
索引访问路径比全表扫描更长,I/O次数更多
4)写密集型场景
每次写操作需要同步更新索引
5)超长文本字段
索引因键值过长导致语义丢失,前缀索引失效;即使索引定位成功,回表获取完整文本需加载多个大字段数据页,I/O开销超过全表扫描
2.适合创建索引的场景
1)高频查询字段
where、join、order by/ group by子句频繁出现的列
2)高区分度字段
数据重复率低,B+树可以精准过滤少量数据,回表成本低于全表扫描
3)大数据量表的查询条件字段
4)覆盖查询字段
索引包含select的所有字段时,引擎直接从索引叶子节点返回数据,消除回表产生的随机I/O
5)外键关联字段
如果没有索引,数据库在执行连接操作时需要进行全表扫描,查找匹配的记录,降低查询性能
五.哪些情况会导致索引失效
索引结构被破坏
1.对字段进行操作导致索引失效
索引存储字段的原始值,并按照B+树组织,实现快速查找。对字段操作后,数据库无法直接匹配索引存储的原始值
1)使用函数或运算处理字段
B+树存储原始列值,如整数100,where id+1=101需要先计算id+1,导致无法匹配索引树节点值(where id*2 >100类似的运算也是同理)
如果使用函数在索引范围之外,即不在查询条件中使用,索引仍然有效
2)隐式类型转换
强制类型转换使索引值失效,如字符串“100”的二进制编码不等于整数100,索引按整型存储,字符串需转换为整型,引入额外的计算,导致优化器放弃索引
3)使用表达式
例如,WHERE salary + bonus > 10000,salary + bonus需计算每行的表达式结果,无法利用索引的有序性
4)WHERE条件中列间比较
SELECT * FROM employees
WHERE salary > bonus;
索引没有存储列间关系的信息,数据库必须获取每一行的实际值来进行列间的比较计算
2.LIKE以%开头
B+树索引按左前缀排序,无法进行后缀匹配
3.联合索引未遵循最左前缀
4.使用ORDER BY可能会导致索引失效
索引失效场景:
1)全字段查询+无过滤条件
SELECT * FROM user ORDER BY card_id;
card_id有索引,但select *需要所有字段,数据库必须回表查询完整数据,当优化器发现回表成本大于全表扫描成本时,放弃索引
2)排序字段与索引顺序矛盾
-- 索引默认ASC,但查询要求DESC
SELECT * FROM user ORDER BY card_id DESC;
索引按升序存储,降序排序需要反向扫描,效率低于全表扫描加排序
3)混合排序字段
-- 索引是单字段,但多字段排序
SELECT * FROM user ORDER BY card_id, age;
索引只能保证card_id有序,无法保证card_id+age有序,需要额外内存排序
索引有效场景:
1)索引覆盖查询
SELECT card_id FROM user ORDER BY card_id;
card_id值已在索引中,只需读取索引,无需回表,直接按索引顺序返回结果
2)where条件过滤+排序
SELECT * FROM user
WHERE card_id > 1000
ORDER BY card_id;
where条件利用索引过滤数据,排序字段与索引一直,且数据量小,回表成本小于全表扫描成本
3)联合索引匹配顺序
-- 创建联合索引
CREATE INDEX idx_card_age ON user(card_id, age);
-- 能走索引
SELECT id FROM user ORDER BY card_id, age;
排序字段顺序与索引定义完全一致,查询字段包含在索引中
索引扫描成本高于全表扫描:
5.IN参数超阈值
where column in (v1,v2,...,vn)当n的值非常大时,优化器预估多次索引查找加上回表的总成本会超过全表扫描的成本
6.错误使用NOT IN或!=
查询条件排除的数据太少,索引查找范围太大,成本高
7.表数据量过小
索引访问路径更长,全表扫描更快
8.IS NULL 或 IS NOT NULL
聚簇索引因为主键约束禁止NULL值,非聚簇索引允许NULL值且将其集中存储在B+树最左侧。行记录中的null bitmap是一个二进制位序列,用来标记表中每一个列是否为null。当null bitmap中对应的位为1时,表示对应的列为null;为0表示列不为null,实现了快速空值判断
对于IS NOT NULL查询,二级索引需要扫描几乎所有条目并触发大量回表随机I/O,达到某个比例时,使用二级索引执行查询的成本超过全表扫描的成本,优化器会在真正执行查询前预计算,走成本小的方式
9.OR连接非索引列
优化器无法合并多个索引扫描路径,如where 索引列A=1 or 非索引列B=2,数据库要么用索引查A=1,这样会漏掉B=2的数据,导致结果错误;要么就全表扫描,放弃索引
10.使用非主键范围条件查询时,部分情况索引失效
1)联合索引中的范围查询使后续索引列失效
SELECT * FROM table WHERE a > 100 AND b = 5;
扫描索引中所有a>100的记录,针对这些记录,数据库逐条检查b=5的记录,如果a>100的记录数量非常大,成本将会非常高
2)范围查询覆盖了表中大部分数据
3)范围查询后,又使用非索引列进行过滤
六.索引对数据库性能的影响
正面影响:
1.加速select查询
避免全表扫描,快速定位数据
2.加速order by排序和group by分组
当order by或group by语句要求的排序顺序与索引存储顺序一致时,数据库可以直接按索引顺序读取数据
3.加速join连接
在连接条件列创建索引,数据库进行join操作时,直接利用索引定位到所有关联值相等的行位置,提高连接速度
4.覆盖索引
查询所需数据全在索引中,避免回表
5.强制唯一性
在列上创建唯一索引后,数据库会强制禁止在该列中插入或修改重复值,确保该列数据唯一性
负面影响:
1.降低写入速度
insert、update、delete操作需要同步更新索引,索引越多开销越大
2.占用磁盘空间
索引是独立的数据结构,消耗额外存储
3.增加优化器负担
过多索引可能使查询优化器选择执行计划更耗时
4.维护成本
索引可能产生碎片,需要定期维护
随着数据的插入、删除和更新,索引数据在物理存储上可能变得不连续,数据页中出现空白空间,这就是索引碎片。碎片会降低索引扫描的效率,需要定期使用OPTIMIZE TABLE 或 ALTER TABLE ... REBUILD 等命令重新整理索引数据来消除碎片
OPTIMIZE TABLE table_name;
这是最直接的表优化命令,重建表数据,重建所有索引,释放未使用的空间
ALTER TABLE table_name ENGINE = INNODB;
创建一张新的、结构相同的空表,将旧表中的数据逐行插入新表中,插入数据的同时重建所有索引,最后删除旧表,将新表重命名为旧表名
七.索引的设计原则有哪些?
1.只为查询频繁的列创建索引
索引主要用于加速查询操作,因此应该为经常出现查询条件的列创建索引,例如出现where子句、join子句、order by子句的列
2.避免为频繁更新的列创建索引
更新操作会导致索引同步更新,会增加写操作的性能开销
3.选择合适的索引类型
4.避免过多索引
过多索引会导致消耗空间,对表进行修改时,相关索引也需要更新
5.选择合适的字段
优先选择数据分布广泛的字段,这样查询时过滤的记录较少。像性别这样的字段,区分度不高,只有“男”、“女”两个值,创建索引的过滤效果不明显
如果低选择性的字段结合其他高选择性的字段一起使用时,索引可能会有效;作为覆盖索引的一部分,也可以提升性能
6.避免为小表创建索引
使用索引开销可能超过全表扫描成本
7.索引字段顺序合理
组合索引时,最常查询的列应靠前
八.通过索引排序内部流程是什么
1.内存优先:
在 Sort Buffer (默认 256KB)中尝试排序,使用快速排序
2.磁盘辅助:
若数据量超过 Sort Buffer,拆分数据到临时文件——>各文件快排——>归并排序合并结果
3.字段优化:
当单行数据长度 > max_length_for_sort_data (默认 4KB)时,触发 RowID 排序:
- 仅加载排序字段 + 主键ID到内存排序
- 排序后根据主键回表查询其他字段
MySQL排序优先用内存快排,内存不足转磁盘归并排序;若单行数据超过4KB(内存页大小),则改用RowID排序,仅存排序字段+主键,排序后回表补数据,最大限度降低内存的压力
九.MySQL 8 的索引跳跃扫描是什么
作用:
解决联合索引中最左列缺失时无法命中索引的问题
传统索引扫描的局限性:
假设联合索引(A,B),执行查询:
select * from table_name where B = XXX;
MySQL 8 之前,因A未出现在查询条件中,索引失效,进行全表扫描
MySQL 8 之后,跳跃扫描:自动识别A的所有可能值,拆解为多个子查询:
select * from table_name where A = XXX and B = XXX;
select * from table_name where A = YYY and B = XXX;
......
实现原理:
1.提取最左列的值,获取所有可能的值
2.为每个值构造虚拟索引
将联合索引(A,B)临时拆解为多个虚拟索引
B + A = XXX
B + A = YYY
3.合并结果
分别将执行子查询后合并结果集,利用索引快速定位B=XXX的行
生效条件:
1.联合索引最左列低选择字段
例如,A表示性别,只有“男”或“女”
2.查询条件包含索引的非最左列
例如,索引(A,B,C),查询条件需包含B或C
3.优化器评估拆分扫描的成本低于全表扫描时才会启用
这是我的自学笔记,目前还在学习阶段,文章中可能有错误和不足,欢迎大家斧正!