MySQL索引详解(重点)

目录

  • 索引简述与背景
    • 认识磁盘
      • MySQL与存储
      • 扇区
      • 系统的IO交互
      • 磁盘随机访问(Random Access)与连续访问(Sequential Access)
    • MySQL 与磁盘交互基本单位
    • MySQL 服务启动后创建Buffer Pool
    • 建立共识
  • 索引详解
    • 理解单个Page
    • 理解多个Page
    • 引入页目录
    • 单页情况
    • 多页情况(浮现B+树)
    • B+树总结
    • ***其他数据结构为何不适用于索引?
    • B树 VS B+树
    • 聚簇索引 VS 非聚簇索引
    • 主键索引VS普通索引
  • 创建索引
      • 创建主键索引
      • 唯一索引的创建
      • 普通索引的创建
      • 索引创建原则
  • 查询索引
  • 删除索引
  • 常见其他索引概念
    • 复合索引
    • 索引覆盖
    • 索引最左匹配原则

索引简述与背景

索引:

提高数据库的性能,索引是物美价廉的东西了。

不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。

但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以索引的价值,在于提高一个海量数据的检索速度

常见的索引分为:

  • 主键索引(primary key)
  • 唯一索引(unique)
  • 普通索引(index)
  • 全文索引(fulltext)–解决子文索引问题。

我们需要从硬件角度,系统角度,应用角度(MySQL是一个应用,其进行IO的起本单位是1page<=>16KB)才能全方位的理解索引

认识磁盘

MySQL与存储

  • MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。
  • 磁盘是计算机中的一个机械设备,相 比于计算机其他电子元件,磁盘效率是比较低的,在加上IO本身的特征,可以知道,如何提交效率,是 MySQL 的一 个重要话题。

下面是磁盘中的一个盘片:

MySQL索引详解(重点)_第1张图片

扇区

数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格子中,就是我们经常所说的扇区。

当然,数据库文件很大,也很多,一定需要占据多个扇区。

所以,最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区

而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。

系统的IO交互

我们现在已经能够在硬件层面定位,任何一个基本数据块了(扇区)。

那么在系统软件上,就直接按照扇区(512字节, 部分4096字节),进行IO交互吗?不是

  • 如果操作系统直接使用硬件提供的数据大小(512字节)进行交互,那么系统的IO代码,就和硬件强相关,换言之,如果硬件发生变化,系统必须跟着变化,耦合度太高;

  • 从目前来看,单次IO 512字节,还是太小了。IO单位小,意味着读取同样的数据内容,需要进行多次磁盘访问,会带来效率的降低

  • 之前学习文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是数据块(文件i节点可以证明)

故,系统读取磁盘,是以块为单位的,基本单位是 4KB

磁盘随机访问(Random Access)与连续访问(Sequential Access)

  • 随机访问:本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大 的移动动作才能重新开始读/写数据。
  • **连续访问:**如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这 样的多个IO操作称为连续访问。

磁盘是通过机械运动(磁头旋转等)进行寻址的,连续访问不需要过多的定位,故效率比较高。

MySQL 与磁盘交互基本单位

而 MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效 率, MySQL 进行IO的基本单位是 16KB (后面统一使用 InnoDB 存储引擎讲解)

mysql> SHOW GLOBAL STATUS LIKE 'innodb_page_size'; -- 查看innodb与磁盘交互单位大小


+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Innodb_page_size | 16384 | -- 16kb*1024=16384字节
+------------------+-------+
1 row in set (0.01 sec)

也就是说,磁盘这个硬件设备的基本单位是 512 字节**,而 MySQL InnoDB引擎 使用 16KB 进行IO交互。**

即, MySQL 和 磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在MySQL 这里叫做page(注意和系统的page 512字节的区分)

用page交互的原因和系统一样,也是一种缓存池的机制,不然查一条就要IO一下,效率也太低下了!

MySQL 服务启动后创建Buffer Pool

类似于一个缓冲区,我们上面讲的其page(16KB),就会一个一个在这个Buffer Pool中进行管理(与系统进行IO); 一般这个Buffer Pool,100多MB;

建立共识

  1. MySQL 中的数据文件,是以系统page为单位保存在磁盘当中的;
  2. MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。(相当于操作系统和底层硬件的一种解耦)
  3. 而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中
  4. 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新 到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是mysql Page。
  5. 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
  6. 为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数;

索引详解

当我们向一个表中插入多条数据,并查看插入结果,如下:

-- 插入多条记录,注意,我们并没有按照主键id的大小顺序插入哦
mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.00 sec)


mysql> select * from user; -- 查看结果,发现竟然默认是有序的!是谁干的呢?排序有什么好处呢?
+----+-----+-----------+
| id | age | name |
+----+-----+-----------+
| 1 | 56 | 欧阳锋 |
| 2 | 26 | 黄蓉 |
| 3 | 18 | 杨过 |
| 4 | 16 | 小龙女 |
| 5 | 36 | 郭靖 |
+----+-----+-----------+
5 rows in set (0.00 sec)

答案:

MySQL缓冲区的每个page 16kb,决定了插入的记录会被自动排序!

理解单个Page

MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要 先描述,在组织 ,我们目前可以简单理解成一个个独立文件是由一个或者多个(该文件比较大)Page构成的。
MySQL索引详解(重点)_第2张图片

不同的 Page ,在 MySQL 中,都是 16KB使用 prev 和 next 构成双向链表

因为有主键的原因, MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的。

页内部存放数据的模块,实质上也是一个链表的结构链表的特点也就是增删快(记录的增删数据库中常用),查询修改慢,所以优化查询的效率是必须 的。

正式因为有序,在查找的时候,从头到后都是有效查找没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程的。

因此,插入数据时排序的目的,就是优化查询的效率(存在页目录等优化手段,要求必须排序)

理解多个Page

  • 通过上面的分析,我们知道,上面页模式中,只有一个功能,就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。 但是,我们也可以看到,现在的页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据
  • 如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起来,而且每个 Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了。 下面引入页目录进一步优化;

MySQL索引详解(重点)_第3张图片

引入页目录

我们日常看书的时候,页号都是连续的,需要找某一章节的内容,我们可以通过前面的目录部分,快速定位页号,进而直接跳转过去,优化效率;

  • 本质上,书中的目录,是多花了纸张的,但是却提高了效率
  • 所以,目录,是一种“空间换时间的做法”

单页情况

那么当前,在一个Page内部,我们引入了目录。比如,我们要查找id=4记录,之前必须线性遍历4次,才能拿到结 果。

现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了, 为何通过键值 MySQL 会自动排序?

  • 方便引入目录,提高查找效率!

多页情况(浮现B+树)

MySQL 中每一页的大小只有 16KB ,单个Page大小固定,所以随着数据量不断增大,1页 16KB 不可能存下所有的数据, 那么必定会有多个页来存储数据。

MySQL索引详解(重点)_第4张图片

单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来。

这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。

可是,貌似这样也有效率问题,在Page 之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测。这 样就显得我们之前的Page内部的目录,有点杯水车薪(有优化,但是不够用)了

  • 那么如何解决呢?解决方案,其实就是我们之前的思路,给Page也带上目录
  • 使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值(key,addr)。
  • 与页内目录不同的地方在于,这种目录管理的级别是页(page)而页内目录管理的级别是行(记录)
  • 其中,每个目录项的构成是:键值+指针。图中没有画全;

存在一个目录页来管理页目录目录页中的数据存放的就是指向的那一页中最小的数据有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page,不需要挨着遍历page寻找了。

其实目录页的本质也是页普通页中存的数据是用户数据(记录),而目录页中只存放普通页的key与addr

  • 可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊? 不用担心,可以再网上累加目录页;

MySQL索引详解(重点)_第5张图片

这就是传说中的B+树!至此,我们已经给我们的表user构建完了主键索引

现在随便找一个id=?我们发现,现在查找的Page数一定减少了,也就意味着mysql与OS的IO次数减少了,那么效率也就提高了。

B+树总结

MySQL索引详解(重点)_第6张图片

  • Page分为目录页数据页目录页 只放各个下级Page的最小键值数据页存放 key与 数据记录
  • 查找的时候,自顶向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数

目录页大小也是16KB,按照key:addr总共16字节来算,(16kb*1024)/16 = 1000多个;

这意味着一个目录页一个page,只存各个下级Page的最小键值能存1000多个! 也就是1000多个page,每个page平均一下少说也能放300多条记录,那么倒数第二级的一个目录页就能管300*1000 = 300000多条记录,可见一般场景,B+树的整体形态会很矮胖,一般算上最下面一层,三层足以构建完成(第一层只有一个入口root page);

也正式B+树的矮胖,意味着我们IO的page次数大大降低,大大提高了查询效率;

***其他数据结构为何不适用于索引?

  • 链表?线性遍历啊
  • 二叉搜索树?插入顺序,退化成链表问题,可能退化成为线性结构;
  • AVL &&红黑树?虽然是平衡或者近似平衡,但是**毕竟是二叉结构,相比较多叉的B+,意味着树整体过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的IO Page交互。**虽然你很秀,但是有更秀的。

Hash?

官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持. (下面的BTREE其实是 B+树;)

MySQL索引详解(重点)_第7张图片
Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在hash面对范围查找就明显不行(hash冲突这些,注定了数据的排序问题,面对一段范围的数据效率就低下了); 还有哈希索引不支持多列联合索引的最左匹配规则,也是排序性导致的原因;

B树 VS B+树

B+树:

MySQL索引详解(重点)_第8张图片

B树?(如下图,每个节点既有index又有value,且之后的节点index不重复出现,且叶子节点相互不连接)

MySQL索引详解(重点)_第9张图片

区别:

  • **节点: ** B树节点,既有数据,又有Page指针; 而B+,只有叶子节点有数据,其他目录页,只有键值和Page指针
  • 叶子节点: B+叶子节点,全部左右相连,而B没有相连;

为何选择B+:

  • B树缺点: 说白了,目录页如果存放key+data的话(B B+的区别1),整体树高度会变高,降低page io的效率;

  • 而且B树的节点之间没有双向连接(B B+的区别2),那么一段范围的数据查询,如果存在多个连续的page中,不能像B+一样直接prev或者next找过去,而是又得从root向下找对应的page,效率不高;

B树优点 : B树的每一个结点都包含key(索引值) 和 value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)

聚簇索引 VS 非聚簇索引

MyISAM 存储引擎-主键索引

MyISAM 引擎同样使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址。下图为 MyISAM 表的主索引, Col1 为主键。

MySQL索引详解(重点)_第10张图片

其中, MyISAM 最大的特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。

相较于 InnoDB 索引, InnoDB 是将索引和数据放在一起的。

查看MyISAM存储引擎下,创建一个表,对应的的文件:
MySQL索引详解(重点)_第11张图片

  • 其中, MyISAM 这种用户数据 与 索引分离的索引方案,叫做非聚簇索引

查看INNODB存储引擎下,创建一个表,对应的的文件:

在这里插入图片描述

  • 其中, InnoDB 这种用户数据与索引数据在一起索引方案,叫做聚簇索引

主键索引VS普通索引

当然, MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以 叫做辅助(普通)索引[比如用户经常用的某个普通字段,那么我们就可以用这个字段建立索引提高查询效率]。

  1. 对于 MyISAM ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复; 叶子data部分保存的都是真正记录的地址

  2. 而对于 INNODB,建立辅助(普通)索引,可以看到, InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值(用于下一步回表查询)

所以INNODB通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键(innodb的索引特殊性,即便没有主键,也会默认生成),然后用主键到主索引中检 索获得记录。这种过程,就叫做回表查询;但不是innodb每次查询都需要回表:

有时候复合索引常用,比如(key1,key2)的索引,我们用key2索引查询key1的时候,这时候查询的字段在索引中已经能拿到了,就不需要回表查询了

  • 为何 InnoDB 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?–>原因就是太浪费空间了,不可能每个普通索引,都需要吧表所有的data拷贝一份,那这么多重复记录可就太冗余了。

创建索引

创建主键索引

  • 有主键就行,mysql就会自动创建主键索引;
-- 方法1.在创建表的时候,直接在字段名后指定 primary key
create table user1(id int primary key, name varchar(30));

-- 方法2.在创建表的最后,指定某列或某几列为主键索引
create table user2(id int, name varchar(30), primary key(id));

-- 方法3. 创建表以后再添加主键
create table user3(id int, name varchar(30));
alter table user3 add primary key(id);

主键索引特点:

  • 一个表中,最多有一个主键索引,当然可以使符合主键
  • 主键索引的效率高(主键不可重复)
  • 创建主键索引的列,它的值不能为null,且不能重复
  • 主键索引的列基本上是int

唯一索引的创建

  • 有唯一键就行,mysql就会自动创建主键索引;
-- 1.在表定义时,在某列后直接指定unique唯一属性。
create table user4(id int primary key, name varchar(30) unique);

-- 2. 创建表时,在表的后面指定某列或某几列为unique
create table user5(id int primary key, name varchar(30), unique(name));

-- 3
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);

唯一索引特点:

  • 一个表中,可以有多个唯一索引
  • 查询效率高(不重复)
  • 如果在某一列建立唯一索引,必须保证这列不能有重复数据;
  • 如果一个唯一索引上指定not null,等价于主键索引(索引层面的等价; 但是唯一键作为指定的一种唯一属性,与用于区分全表记录的主键还是不同的概念)

普通索引的创建

  • 普通索引就需要 index(字段)显示创建啦;
-- 方法1
create table user8(id int primary key,
name varchar(20),
email varchar(30),
index(name) -- 在表的定义最后,指定某列为索引
);

-- 方法2
create table user9(id int primary key, name varchar(20), email varchar(30));
alter table user9 add index(name); -- 创建完表以后指定某列为普通索引

-- 方法3
create table user10(id int primary key, name varchar(20), email varchar(30));
create index idx_name on user10(name);-- 创建一个索引名为 idx_name 的索引

普通索引的特点:

  • 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多;(比如qq名字,就不是主键也不是唯一键,可以重复!但我们也经常用类似这样的普通索引查找)
  • 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引

索引创建原则

  • 比较频繁作为查询条件的字段应该创建索引;
  • 唯一性太差的字段不适合单独创建索引(即使频繁作为查询条件); eg:性别,非难即女,作为索引有啥用呢,提高不了效率;
  • 更新非常频繁的字段不适合作创建索引; eg:索引的更新调整也是需要消耗时间的!
  • 不会出现在where子句中的字段不该创建索引 eg:都不会出现where,用来筛选记录,还给他创建提高筛选效率的索引干嘛?

查询索引

show index from goods\G;

*********** 1. row ***********
Table: goods -- 表名
Non_unique: 0 -- 0表示唯一索引
Key_name: PRIMARY -- 主键索引
Seq_in_index: 1
Column_name: goods_id -- 索引在哪列
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE -- 以B+树形式的索引
Comment:
1 row in set (0.00 sec)

删除索引

-- 方法一:删除主键索引
alter table 表名 drop primary key;
-- 方法一:删除唯一索引 or 其他索引
alter table 表名 drop index 索引名; 

常见其他索引概念

复合索引

将数据库中的多个字段组合起来形成的一个索引就是复合索引

-- 创建一个 复合的 唯一索引
UNIQUE key 'idex_test'('name','age') USING BTREE.
  • 使用复合索引的目的: 能够形成索引覆盖,提高where查询的效率 (但注意,并不是越多越好,更新数据会更新索引,有代价的)
  • 复合索引 一个充当好几种索引。index (a,b,c)为例建立这样的索引相当于建立了索引a、ab、abc三个索引。 一个索引顶三个索引当然是好事,毕竟每多一个索引,都会增加写操作的开销和磁盘空间的开销

索引覆盖

覆盖索引是一种数据查询方式;

在索引中,通过索引值可以直接找到要查询字段的值,而不需要通过主键值回表查询,那么就叫覆盖索引; ,

类似于INNODB中使用复合索引(name,age);

我们查询 select name, age from user where name = ‘xxx’; 那么liudehua匹配到(name,age)中的name时候,进而匹配到(name,age)索引,不需要拿到对应的主键key回表查询,也能拿到需要查询的age,就不用再回表查询了–>索引覆盖;

索引最左匹配原则

在SQL语句中,复合索引的第一个字段必须出现查询语句中这样索引才能够被使用。否则,最左字段不出现在where的查询语句,则不会走索引;(explain 关键字可证明;)explain显示了mysql如何使用索引来处理select语句以及连接表的相关信息

  • mysql创建联合索引时,首先会对最左边字段排序,也就是第一个字段,然后再在保证第一个字段有序的情况下,再排序第二个字段,多字段顺序以此类推。

你可能感兴趣的:(MySQL数据库,mysql,数据库,java)