索引
索引概念
索引
:提高数据库的性能,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度
常见索引分为:
- 主键索引(primary key)
- 唯一索引(unique)
- 普通索引(index)
- 全文索引(fulltext)–解决中子文索引问题
索引的速度:
- 例子:海量数据查询
建立索引:alter table EMP add index(empno);
之后查询速度很快
磁盘与MySQL
MySQL 与磁盘交互基本单位为16kb
磁盘扇区
:
以前的扇区通常为512字节,而现在通常为4096字节,也叫4k扇区
。数据库文件,本质其实就是保存在磁盘的盘片(扇区)当中,数据库文件很大,也很多,一定需要占据多个扇区,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区
问
:
- 我们现在已经能够在硬件层面定位,任何一个基本数据块了(扇区)。那么在系统软件上,就直接按照扇区(512字节,部分4096字节),进行IO交互吗?
答:
- 如果操作系统直接使用硬件提供的数据大小进行交互,那么系统的IO代码,就和硬件强相关,换言之,如果硬件发生变化,系统必须跟着变化
- 从目前来看,单次IO 512字节,还是太小了。IO单位小,意味着读取同样的数据内容,需要进行多次磁盘访问,会带来效率的降低
- 文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是数据块,
系统读取磁盘,是以块为单位的,基本单位是 4KB
MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率, MySQL 进行IO的基本单位是 16KB
,MySQL InnoDB引擎 使用 16KB 进行IO交互,即MySQL 和磁盘进行数据交互的基本单位是 16KB ,这个基本数据单元,在 MySQL 这里叫做page
磁盘随机访问(Random Access):
- 本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据
连续访问(Sequential Access):
- 如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个IO操作称为连续访问
所以尽管相邻的两次IO操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话也只能称为随机访问,而非连续访问,磁盘是通过机械运动进行寻址的,随机访问不需要过多的定位,故效率比较高
总结:
- MySQL 中的数据文件,是以page为单位保存在磁盘当中的。
- MySQL 的 增删改查(CURD) 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。
- 只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够
先将数据移动到内存当中
。- 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时
IO的基本单位就是Page
。- 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就
申请了被称为 Buffer Pool 的的大内存空间
,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。- 为了更高的效率,一定要
尽可能的减少系统和磁盘IO的次数
注意
: Buffer Pool更多底层细节参考:一文了解MySQL的Buffer Pool
mysql中对任何数据进行修改(所有CURD操作),不是直接对磁盘文件进行任何修改,而是在内存中对对应的page进行修改,然后使用特定.的刷新策咯刷新到磁盘中
,为此Innodb 存储引擎设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能默认配置下 Buffer Pool 只有 128MB,当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘
理解索引
为什么mysql以page(16kb)与磁盘进行IO
,往往IO效率低下的最主要矛盾不是IO单次数据量的大小,而是IO的次数,由局部性原理:虽然不能严格保证用户下一次寻找的数据就在这个page里,但是有很大概率不用进行多余的IO操作
向表中主键乱序插入一堆数据,再select查看数据可以发现是主键有序的
为什么有序?
- 插入数据时排序的目的,就是优化查询的效率,页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以必须进行优化,排序后,在查找的时候,从头到后都是有效查找,没有任何一个查找是多余的
- 目录需要(详见下面)
mysql中需要管理很多数据表文件,所以需要将这些文件进行组织起来方便管理,可以简单理解成一个个独立文件是有一个或者多个Page构成的
对于多个page,在 MySQL 中,使用 prev 和 next 构成双向链表
页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据,当数据量过大时,查找特定一条记录,也一定是线性查找,效率太低
page引入目录
page内引入目录
空间换取时间,提高页内查询的效率
(类比书的目录)
这也是必须有序的原因
在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针
的方式,将所有的Page组织起来
注意
:要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示
当数据过多的时候,会出现多个page,在Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测,
这时就需要给Page也带上目录
- 使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值
- 和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行
- 其中,每个目录项的构成是:键值+指针(图中省略)
一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据,目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址
当顶层的目录页过多,再建立目录页的目录页,这种结构也就是B+树
在最开始只需要将最上面的page加载到内存,若查找的下一级 page在目录1和目录2之间,只需要加载左半部分的page到内存,以此类推
当我们真正访问某些物理内存时,需要建立映射关系时,对应的页表条目才需要被加载
为什么不在目录页中分担一些数据?
- 在mysql中如果目录页也保存数据,16kb中,能够作为目录项的内容就变少,目录页管理的page也变少了
官方的索引实现方式中, MySQL支持HASH,InnoDB 和 MyISAM 不支持
hash在范围查找中效率低下
,哈希索引只适用于等值查询的场景,哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值的情况下,哈希索引的效率会很低,因为存在哈希碰撞问题
为什么MySQL不使用B树
B树
:非叶子节点中既有键值(表中记录的主键)和指针,也有data,叶子节点只有键值和data
B+树
:只有叶子节点才有data
节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少,叶子节点相连,更便于进行范围查找
聚簇索引和非聚簇索引、回表
聚簇索引
:data存放在叶子节点中(索引和数据放在一起)----InnoDB
非聚簇索引
:在叶子节点中不包含数据,data域存放的是数据记录的地址(索引和数据分离)----MyISAM
- 如图
参考之前看到的MyISAM引擎和InnoDB不同的配置文件:
- .frm文件:表结构
- MYD的D:data
- MYI的I:Index
- .ibd文件:Index+Data
处理主键索引还有辅助索引(
用户也有可能建立按照 其他列 信息建立的索引
):
- 对于MyISAM来说建立索引就是新增B+树,辅助索引和主键索引没有区别 (浪费空间)
- 对于InnoDB,辅助索引的叶子节点只保存当前列的对应的每个主键(例如:建立name的辅助索引:叶子节点的data域保存的是对应的所有name的主键id值)----
这也就是回表查询:通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录
索引操作
创建主键索引
- 在字段名后指定 primary key:
- 在末尾指定主键索引:
- 使用alter添加主键:
注意:
- 一个表中,只能有一个主键索引,当然可以使符合主键
- 主键索引的效率高(主键不可重复)
- 创建主键索引的列,它的值不能为null,且不能重复
- 主键索引的列基本上是int
创建唯一键索引
- 在某列后直接指定unique唯一属性:
- 表末尾指定某列或某几列为unique:
- 使用alter添加唯一键:
注意:
- . 一个表中,可以有多个唯一索引
- . 查询效率高
- . 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- . 如果一个唯一索引上指定not null,等价于主键索引
创建普通索引
- 表末尾指定某列为索引
- 指定某列为普通索引
- 创建指定名称的索引
show查看
注意:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
创建全文索引
全文索引:
- 当对文章字段或有大量文字的字段进行检索时,使用全文索引,MySQL将把在文本中出现的所有单词组合列出来,创建出一个集合,之后的查询操作会在这个集合中进行检索 (
注意:存储引擎必须是MyISAM,且默认的全文索引支持英文,不支持中文,中文可使用sphinx的中文版coreseek
)
例如:
- 在分词大全中进行找到文章编号的叫做
倒排索引
(反之通过编号找文章的叫做正排索引)
上图的整个操作就是对文章建立全文索引
当我们查找文章中是否出现 is 时,得到的反馈是1,2号文章都有
建立全文索引:
- 随数据表一同创建
- 创建表之后使用alter添加
全文索引的使用
以上面创建的table10为例:
插入数据,select查询包含e的字段:
explain查看是否使用全文索引:
正确使用全文索引:
注意:
这里使用了全文索引后并不会找到,因为列出的集合并不包括e单独的字母,一般是一个或多个单词的组合
explain验证:
创建复合索引与索引覆盖
单一索引是指索引列为一列的情况,另外,用户可以在多个列上建立索引,这种索引叫做复合索引,也叫组合索引,复合索引在数据库操作期间所需的开销更小,可以代替多个单一索引
创建:
使用:
回表:
- 由于辅助索引只存储主键的值,如果使用辅助索引搜索数据就必须先从辅助索引取到主键的值,再使用主键的值去主键索引上查询,直到找到叶子节点上的数据返回
覆盖索引
:
- 如果辅助索引上已经存在我们需要的数据,那么引擎就不会去主键上去搜索数据了,即,在一棵索引树上就能获取sql所需的所有列数据,无需回表,速度更快
例如
:以table12为例查询id,对于复合索引(name,age,sex)通过普通索引 age=12 定位到主键值id,无需回表,将被查询的字段,建立到复合索引里去
可以看到,在查询的列中增加了不是索引的qq后不会使用索引覆盖
注意
:
Using where:不是索引,全表扫描后,通过过滤获取所需数据
Using index:查询使用索引就能直接访问索引文件获取到所需要的数据,即使用的是索引覆盖
其他参考:mysql中using where,using index
索引最左匹配原则
最左匹配原则主要针对复合索引
构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树
,例如(a, b, c)即 以a来构建,a有序,b, c无序
全匹配
:MySQL会自动优化索引顺序,均会使用索引(这里的索引并不是索引覆盖)连续,匹配最左列
:均使用索引连续,没有匹配最左列
:不使用索引不连续匹配时
:只使用最左边的单索引(filtered列查看占比)模糊匹配
:只有前缀(a%)使用索引, 中缀(%a%)后缀(%a)不使用索引精确匹配最左列,范围匹配其他列
:使用最左列索引范围匹配最左列
:使用索引范围匹配最左列,范围匹配其他列
:使用最左列索引
具体参考:联合索引-最左匹配原则
删除索引
- 删除主键索引:
alter table (tablename) drop primary key;
- 其他索引的删除:
alter table (tablename) drop index (indexname);
indexname即show keys from 表名中Key_name 字段drop index (indexname) on (tablename);
查询索引
show keys from (tablename)
其中non_unique为0表示唯一索引,column_name表示索引在哪一列,index_type表示以什么形式 (如底层二叉树)
show index from (tablename)
desc (tablename)
总结
创建索引规则:
- 比较频繁作为查询条件的字段应该创建索引
- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
- 更新非常频繁的字段不适合作创建索引
- 不会出现在where子句中的字段不该创建索引
其他:
外键索引 :
- 如果为某个外键字段定义了一个外键约束条件,MySQL就会定义一个内部索引来帮助自己以最有效率的方式去管理和使用外键约束条件