9.MySQL索引
索引意义和分类
索引:价值在于提高一个海量数据的检索速度。查询速度的提高是以插入、更新、删除的速度为代价的(数据库引擎不仅要修改原始数据表(Table),还必须同步更新所有相关的索引结构,以保持数据和索引的一致性)
索引分类:
- 主键索引(primary key)
- 唯一索引(unique)
- 普通索引(index)
- 全文索引(fulltext)--解决中文索引问题
认识磁盘
最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。
磁盘:CHS定址,磁头定柱面,柱面定磁道,磁道定扇区
系统读取磁盘,是以块为单位的,基本单位是 4KB(出于效率和解耦)随机访问 vs 连续访问
- 随机访问:本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据。
- 连续访问:如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个IO操作称为连续访问。
- 因此尽管相邻的两次IO操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话也只能称为随机访问,而非连续访问。
- 磁盘是通过机械运动进行寻址的,随机访问不需要过多的定位,故效率比较高。
总结:随机访问磁头需要大幅度移动寻址,连续访问不用,连续访问因此效率高。
理解索引设计缘由
MySQL服务器,本质是在内存中的,所有数据库的CURD操作,都是在内存中进行的!--- 索引也是如此。
提高算法效率的因素:
1)组织数据的方式
2)算法本身
MySQL与磁盘交互基本单位
mysql是应用层的服务,由OS体系结构可以肯定,mysql访问磁盘(硬件)必须贯穿OS,通过内核文件缓冲区进行读取数据。数据需要持久化时就调用 fsync(int fd)刷新到外设(磁盘)。
mysql进行IO的基本单位是16KB。这个基本数据单元,在 MySQL 这里叫做page。
mysql启动时会开辟一块缓冲区,大小默认是128MB。
建立共识
mysql数据文件,都是以page为单位保存在磁盘中的
如何让IO变高效?尽可能减少系统和磁盘IO次数(磁盘寻址很慢,数据拷贝还行 -> 减少寻址次数)
page理解
问题1:为什么向一个具有主键列表中,插入乱序主键字段值,最后按主键排序了?
mysql自己做的,可以很方便引入目录(即使不创建主键,mysql会创建隐藏列作为主键)
为何IO交互要是 Page
page大小16KB,一次加载大的数据,且因为有局部性原理,加载的数据往往可能下次被用到,可以减少IO次数。往往IO效率低下的最主要矛盾不是IO单次数据量的大小,而是IO的次数。
问题2:page理解
mysql内部,一定会存在大量page,必须要管理起来 -> 先描述,在组织。
假设page就是双向链式结构,
struct page
{
struct *page prev;
struct *page next;
char[NUM] buff;
}
一个page16KB,mysql通过双向链表管理在buffer-pool内部。
效率问题:
但是链式结构的查找效率太低了,如何改良?
1)给buff加上目录(每个目录项的构成是:键值+指针)
这样一来,查找时就能跳着找,效率翻了2倍。但效率还是比较低,时间复杂度没有变
2)拓展一下
这样一来,单次筛选淘汰数量变多了,查找效率变快了。而且只让最底层page存数据,那么非叶子page就可以存更多的目录。
引入数据结构,B+树(链接:4.B-树-CSDN博客)
mysql innodb下的索引结构是B+树(如果建表没有指定主键,会有默认主键)
索引本质就是数据结构B+树(时间复杂度logM(N),M为目录项个数)1.叶子节点保存有数据,路上节点没有;非叶子节点,不要数据,只要目录项。非叶子节点,由于不存数据,可以存储更多的目录项,目录页,可以管理更多的叶子page,这课树,一定是一个 “矮胖” 的树 -> 途径路上节点,减少 -> 找到目标数据只需要更少的page!IO次数更少,IO层面,提高了效率
2.叶子节点全部用链表级联起来原因
a.首先这就是B+树特点
b.我们比较希望进行范围查找(即10~20之间数据,只需找10和20确定边界,其余靠遍历O(1))
为什么其他数据结构不行?
- AVL &&红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+,意味着树整体过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的IO Page交互。
- Hash?官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持.Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在面对范围查找就明显不行(Hash不适合范围查找)
B树?最值得比较的是 InnoDB 为何不用B树作为底层索引?
B树节点,既有数据,又有Page指针,而B+,只有叶子节点有数据,其他目录页,只有键值和Page指针。那么这就意味着B树树高会比B+树更高,目录项存键值会变少,IO次数变多;而且B树也不方便范围查找;因此效率没有B+树高。
聚簇索引和非聚簇索引
MyISAM是采用叶子节点数据存数据地址的方式,将key值与数据分离(非聚簇索引)。
而Innodb采用的是key与数据放一起的方式,聚簇索引。
MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引。
- 对于 MyISAM ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。
- InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值(主键索引)。所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询
为何 InnoDB 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?原因就是太浪费空间了。
索引操作
创建主键索引:
1)建表时直接跟在属性列后面,例:id int primary key
2)建表时指定在表内末尾,例:primary key(id)
3)建表后添加:alter table 表名 add primary key(id)
主键索引的特点:
- 一个表中,最多有一个主键索引,当然可以使符合主键
- 主键索引的效率高(主键不可重复)
- 创建主键索引的列,它的值不能为null,且不能重复
- 主键索引的列基本上是int
创建唯一键索引:
1)建表时跟属性列后,例:name varchar(20) unique
2)建表时指定在表内末尾,例:unique(name)
3)建表后添加:alter table 表名 add unique(name)
唯一索引特点(唯一键):
- 一个表中,可以有多个唯一索引
- 查询效率高
- 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- 如果一个唯一索引上指定not null,等价于主键索引
创建普通索引:
1)创建表时指定在表内末尾,例:index(name)2)建表后添加,例:alter table 表名 add index(name)
3)指定索引名创建,例:create index 要创建的索引名 on 表名(name)
普通索引的特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
创建全文索引:
前提条件:表的存储引擎必须是MyISAM,默认全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx的中文版(coreseek)创建表时指定在表内末尾:FULLTEXT (字段1,字段2,...)
特别注意:使用全文索引时,如果不指定索引,默认是不用的
查看sql语句执行方式:explain select语句
key为NULL表示没有使用到索引
使用全文索引方式:select * from 表名 where MATCH(字段1,字段2...) AGAINST('database') 其中字段为创建全文索引时的字段
查询索引:
1)show keys from 表名
2)show index from 表名
3)desc 表名
删除索引:
1)删除主键索引:alter table 表名 drop primary key
2)删除唯一索引,普通索引,全文索引:alter table 表名 index 索引名
3)drop index 索引名 from 表名
索引创建原则
- 比较频繁作为查询条件的字段应该创建索引
- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
- 更新非常频繁的字段不适合作创建索引
- 不会出现在where子句中的字段不该创建索引
复合索引:即索引指定两个及两个以上字段,这两个字段一起充当key(类比复合主键)
索引最左匹配原则:可以只给出最左字段进行查找
索引覆盖:解决非聚簇索引回表导致效率低问题
例子:user表:id (主键) ,name,age,city。
给name创建了一个索引
查询:SELECT age, city FROM users WHERE name = ‘李四’;
由于是非聚簇索引,name是普通索引,最终会拿到其对应主键id,进行回表。
回表操作是非常影响效率的,再查一次,途径路上节点需要IO。
解决方法核心:从key上直接获取数据,避免回表
给name,age,city三个创建一个复合索引。
这时候在以name为条件,最左匹配原则查找时,找到了,就可以直接拿到age和city,不用再进行回表了。
大幅提升查询性能:这是最核心的优势。由于避免了耗时的回表操作(尤其是随机I/O),查询速度可以得到数量级的提升。