1、每个数据页可以组成一个双向列表
2、每个数据页中的记录又可以组成一个单向列表
使用索引之后,将无序的数据变成有序(相对)
没有使用索引时,遍历双向链表-->定位对应的页
有了索引,可以通过“目录”,定位到对应到页。
很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过目录可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))。
其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。
哈希索引
BTree索引
对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。
这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。
在根据主索引搜索时,直接找到key所在的节点即可取出数据;
在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
联合索引: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
在查询数据的时候:要查询出的列在叶子节点都存在!所以,就不用回表。
如何实现索引覆盖?
哪些场景,可以利用索引覆盖来优化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。
原表为:
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
优化引用,回表查询