索引的优缺点
优点:
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 减少 IO 次数,这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点:
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
但是,使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
底层数据结构
MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样
实现区别
MyISAM 引擎中,一律使用"非聚簇索引(非聚集索引) ",也就是说叶子节点中的data存放的不是主键值,而是对应数据的地址,有回表的性能损失。
InnoDB 引擎中,以主键为索引时,叶子节点的data直接存放数据库主键值 ,称为"聚簇索引(聚集索引) ",而其余的非主键索引 都作为 辅助索引 ,辅助索引的 data 域存储主键值而不是当前索引列的值;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在辅助索引中也会存在回表(覆盖索引可解决)。
注意: Mysql官方文档中介绍,聚集索引其实就是指的主键索引,也就是说只有Innodb中的主键索引才能叫做聚集索引,其余均为非聚集索引。
B树& B+树
B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 Balanced
(平衡)的意思。
B树& B+树两者有何异同呢?
-
B树所有节点都存放key+value
-
B+树只有叶子节点存放key+value,其余节点存放key
-
B+树在叶子节点间有一条引用链 ,在进行范围查找时只需要对链表进行遍历,不再需要从根节点到叶子节点进行二分查找
注意: 二叉树为什么这么快-> 使用二分查找,时间复杂度:O(logn)。
索引的类别
按照底层存储方式角度划分:
- 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
- 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
- 普通索引:仅加速查询。
- 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
聚簇索引一定回表查询吗(覆盖索引)?
非聚簇索引不一定回表查询。
试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。
sql
SELECT name FROM table WHERE name='guang19';
那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!如果 SQL 查的就是主键呢?
sql
SELECT id FROM table WHERE id=1;
总结:一般索引运用于"where"条件,使用非聚集索引查询具体字段时,会发生回表。但如果将select后的字段也建立索引,就可以直接返回key里的值,无需根据主键回表
详情可见:MySQL 覆盖索引详解 - 掘金 (juejin.cn)
联合索引
考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
最左匹配原则
你可以认为联合索引是闯关游戏的设计
例如你这个联合索引是 state/city/zipCode
那么 state 就是第一关 city 是第二关, zipCode 就是第三关
你必须匹配了第一关,才能匹配第二关,匹配了第一关和第二关,才能匹配第三关
索引下推
索引下推(Index Condition Pushdown,简称 ICP) 是 MySQL 5.6 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 WHERE
字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
假设我们有一个名为 user
的表,其中包含 id
, username
, zipcode
和 birthdate
4 个字段,创建了联合索引(zipcode, birthdate)
。
sql
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`zipcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`birthdate` date NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_username_birthdate` (`zipcode`,`birthdate`) ) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4;
# 查询 zipcode 为 431200 且生日在 3 月的用户
# birthdate 字段使用函数索引失效
SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;
没有索引下推之前:
- 存储引擎层先根据
zipcode
索引字段找到所有zipcode = '431200'
的用户的主键 ID,然后二次回表查询,获取完整的用户数据; - 存储引擎层把所有
zipcode = '431200'
的用户数据全部交给 Server 层,Server 层根据MONTH(birthdate) = 12
这一条件再进一步做筛选。
有了索引下推之后:
- 存储引擎层先根据
zipcode
索引字段找到所有zipcode = '431200'
的用户,然后直接判断MONTH(birthdate) = 12
,筛选出符合条件的主键 ID; - 二次回表查询,根据符合条件的主键 ID 去获取完整的用户数据;
- 存储引擎层把符合条件的用户数据全部交给 Server 层。
总结:索引下推可以减少回表次数,根据where里的查询条件筛选,避免回表的全盘扫描
索引失效
避免索引失效
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
使用SELECT *
进行查询;SELECT *
不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖;- 创建了组合索引,但查询条件未遵守最左匹配原则;
- 在索引列上进行计算、函数、类型转换等操作;
- 以 % 开头的 LIKE 查询比如
LIKE '%abc';
; - 查询条件中使用 OR,且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
- IN 的取值范围较大时会导致索引失效,走全表扫描(NOT IN 和 IN 的失效场景相同);
- 发生隐式转换open in new window;
- ......