索引原理
1、每个数据页可以组成一个双向列表
2、每个数据页中的记录又可以组成一个单向列表
- 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
- 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录
所以说,如果我们写select * from user where indexname = 'xxx’这样没有进行任何优化的sql语句,默认会这样做:
1.定位到记录所在的页:需要遍历双向链表,找到所在的页
2.从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了
很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。
使用索引之后,将无序的数据变成有序(相对)
没有使用索引时,遍历双向链表-->定位对应的页
有了索引,可以通过“目录”,定位到对应到页。
很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过目录可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))。
其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。
Mysql索引主要使用的两种数据结构
-
哈希索引
-
BTree索引
对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
MyISAM和InnoDB实现BTree索引方式的区别
MyISAM
B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
InnoDB
其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。
这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。
在根据主索引搜索时,直接找到key所在的节点即可取出数据;
在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
索引遵循的原则
- 最左前缀原则
- 注意避免冗余索引
- 添加索引
- 1.添加PRIMARY KEY(主键索引)
- 2.添加UNIQUE(唯一索引)
- 3.添加INDEX(普通索引)
- 4.添加FULLTEXT(全文索引)
- 5.添加多列索引
- 最左前缀
联合索引:MySQL中的索引可以按一定顺序引用多列,这种索引就叫联合索引。
如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:
select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引
select * from user where city=xx; // 无法命中索引
这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 city= xx and name =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的.
由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDERBY子句也遵循此规则。
2、注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
3、添加索引
(1)主键
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
(2) 唯一
ALTER TABLE `table_name` ADD UNIQUE ( `column` )
(3)普通
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
(4)全文
ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
(5)联合
ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
覆盖索引介绍
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。
InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢,覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
索引覆盖实例:
现在我创建了索引(username,age),我们执行下面的 sql 语句
select username , age from user where username = 'Java' and age = 22
在查询数据的时候:要查询出的列在叶子节点都存在!所以,就不用回表。
选择索引和编写利用这些索引的查询的3个原则
- 单行访问是很慢的。特别是在机械硬盘存储中(SSD的随机I/O要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。使用索引可以创建位置引,用以提升效率。
- 按顺序访问范围数据是很快的,这有两个原因。第一,顺序1/0不需要多次磁盘寻道,所以比随机I/O要快很多(特别是对机械硬盘)。第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUPBY查询也无须再做排序和将行按组进行聚合计算了。
- 索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就 不需要再回表查找行。这避免了大量的单行访问,而上面的第1点已经写明单行访 问是很慢的。
优化:如何避免回表查询?什么是索引覆盖?
如何实现索引覆盖?
哪些场景,可以利用索引覆盖来优化SQL?
create table user (
id int primary key,
name varchar(20),
sex varchar(5),
index(name)
)engine=innodb;
select
id,
name
where
name
=
'shenjian'
select
id,
name
,sex
where
name
=
'shenjian'
多查询了一个属性,为何检索过程完全不同?
InnoDB索引
-
聚集索引(clustered index)
-
普通索引(secondary index)
InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:所以PK查询非常快,直接定位行记录。
InnoDB普通索引的叶子节点存储主键值 主键+列值。(不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针。
)
(1)如果表定义了PK,则PK就是聚集索引;
(2)如果表没有定义PK,则第一个not NULL unique列是聚集索引;
(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
t(id PK, name KEY, sex, flag);
表中有四条记录:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
两个B+树索引分别如上图:
(1)id为PK,聚集索引,叶子节点存储行记录;
(2)name为KEY,普通索引,叶子节点存储PK值,即id;
既然从普通索引无法直接定位行记录,那普通索引的查询过程是怎么样的呢?
通常情况下,需要扫码两遍索引树。
例如:
select
*
from
t
where
name
=
'lisi'
;
如粉红色路径,需要扫码两遍索引树:
(1)先通过普通索引定位到主键值id=5;
(2)在通过聚集索引定位到行记录;
这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
MySQL官网,类似的说法出现在explain查询计划优化章节,即explain的输出结果Extra字段为Using index时,能够触发索引覆盖。
不管是SQL-Server官网,还是MySQL官网,都表达了:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
如何实现索引覆盖??
常见的方法是:将被查询的字段,建立到联合索引里去。
create
table
user
(
id
int
primary
key
,
name
varchar
(20),
sex
varchar
(5),
index
(
name
)
)engine=innodb;
之前到例子
能够命中name索引,索引叶子节点存储了主键id,通过name的索引树即可获取id和name,无需回表,符合索引覆盖,效率较高。
画外音,Extra:Using index。
能够命中name索引,索引叶子节点存储了主键id,但sex字段必须回表查询才能获取到,不符合索引覆盖,需要再次通过id值扫码聚集索引获取sex字段,效率会降低。
画外音,Extra:Using index condition。
如果把(name)单列索引升级为联合索引(name, sex)就不同了。
create
table
user
(
id
int
primary
key
,
name
varchar
(20),
sex
varchar
(5),
index
(
name
, sex)
)engine=innodb;
都能够命中索引覆盖,无需回表。
画外音,Extra:Using index。
哪些场景可以利用索引覆盖来优化SQL?
- 场景1:全表count查询优化
- 场景2:列查询回表优化
- 场景3:分页查询
原表为:
user(PK id, name, sex);
直接:
1 |
|
不能利用索引覆盖。
添加索引:
1 |
|
就能够利用索引覆盖提效。
场景2:列查询回表优化
1 |
|
这个例子不再赘述,将单列索引(name)升级为联合索引(name, sex),即可避免回表。
场景3:分页查询
1 |
|
将单列索引(name)升级为联合索引(name, sex),也可以避免回表。
InnoDB聚集索引普通索引,回表,索引覆盖
引用:
https://github.com/Snailclimb/JavaGuide/blob/master/docs/database/MySQL%20Index.md