索引:
提高数据库的性能,索引是物美价廉的东西了。
不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。
但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以索引的价值,在于提高一个海量数据的检索速度。
常见的索引分为:
我们需要从硬件角度,系统角度,应用角度(MySQL是一个应用,其进行IO的起本单位是1page<=>16KB)才能全方位的理解索引
下面是磁盘中的一个盘片:
数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格子中,就是我们经常所说的扇区。
当然,数据库文件很大,也很多,一定需要占据多个扇区。
所以,最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。
而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。
我们现在已经能够在硬件层面定位,任何一个基本数据块了(扇区)。
那么在系统软件上,就直接按照扇区(512字节, 部分4096字节),进行IO交互吗?不是
如果操作系统直接使用硬件提供的数据大小(512字节)进行交互,那么系统的IO代码,就和硬件强相关,换言之,如果硬件发生变化,系统必须跟着变化,耦合度太高;
从目前来看,单次IO 512字节,还是太小了。IO单位小,意味着读取同样的数据内容,需要进行多次磁盘访问,会带来效率的降低。
之前学习文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是数据块(文件i节点可以证明)。
故,系统读取磁盘,是以块为单位的,基本单位是 4KB 。
磁盘是通过机械运动(磁头旋转等)进行寻址的,连续访问不需要过多的定位,故效率比较高。
而 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一下,效率也太低下了!
类似于一个缓冲区,我们上面讲的其page(16KB),就会一个一个在这个Buffer Pool中进行管理(与系统进行IO); 一般这个Buffer Pool,100多MB;
当我们向一个表中插入多条数据,并查看插入结果,如下:
-- 插入多条记录,注意,我们并没有按照主键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,决定了插入的记录会被自动排序!
MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要 先描述,在组织 ,我们目前可以简单理解成一个个独立文件是由一个或者多个(该文件比较大)Page构成的。
不同的 Page ,在 MySQL 中,都是 16KB ,使用 prev 和 next 构成双向链表
因为有主键的原因, MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的。
页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快(记录的增删数据库中常用),查询修改慢,所以优化查询的效率是必须 的。
正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程的。
因此,插入数据时排序的目的,就是优化查询的效率(存在页目录等优化手段,要求必须排序)
我们日常看书的时候,页号都是连续的,需要找某一章节的内容,我们可以通过前面的目录部分,快速定位页号,进而直接跳转过去,优化效率;
- 本质上,书中的目录,是多花了纸张的,但是却提高了效率
- 所以,目录,是一种“空间换时间的做法”
那么当前,在一个Page内部,我们引入了目录。比如,我们要查找id=4记录,之前必须线性遍历4次,才能拿到结 果。
现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了, 为何通过键值 MySQL 会自动排序?
MySQL 中每一页的大小只有 16KB ,单个Page大小固定,所以随着数据量不断增大,1页 16KB 不可能存下所有的数据, 那么必定会有多个页来存储数据。
单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来。
这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。
可是,貌似这样也有效率问题,在Page 之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测。这 样就显得我们之前的Page内部的目录,有点杯水车薪(有优化,但是不够用)了
- 使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值(key,addr)。
- 与页内目录不同的地方在于,这种目录管理的级别是页(page),而页内目录管理的级别是行(记录)。
- 其中,每个目录项的构成是:键值+指针。图中没有画全;
存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page,不需要挨着遍历page寻找了。
其实目录页的本质也是页,普通页中存的数据是用户数据(记录),而目录页中只存放普通页的key与addr。
这就是传说中的B+树!至此,我们已经给我们的表user构建完了主键索引。
现在随便找一个id=?我们发现,现在查找的Page数一定减少了,也就意味着mysql与OS的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次数大大降低,大大提高了查询效率;
Hash?
官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持. (下面的BTREE其实是 B+树;)
Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在hash面对范围查找就明显不行(hash冲突这些,注定了数据的排序问题,面对一段范围的数据效率就低下了); 还有哈希索引不支持多列联合索引的最左匹配规则,也是排序性导致的原因;
B+树:
B树?(如下图,每个节点既有index又有value,且之后的节点index不重复出现,且叶子节点相互不连接)
区别:
为何选择B+:
B树缺点: 说白了,目录页如果存放key+data的话(B B+的区别1),整体树高度会变高,降低page io的效率;
而且B树的节点之间没有双向连接(B B+的区别2),那么一段范围的数据查询,如果存在多个连续的page中,不能像B+一样直接prev或者next找过去,而是又得从root向下找对应的page,效率不高;
B树优点 : B树的每一个结点都包含key(索引值) 和 value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)
MyISAM 存储引擎-主键索引
MyISAM 引擎同样使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址。下图为 MyISAM 表的主索引, Col1 为主键。
其中, MyISAM 最大的特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。
相较于 InnoDB 索引, InnoDB 是将索引和数据放在一起的。
查看INNODB存储引擎下,创建一个表,对应的的文件:
当然, MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以 叫做辅助(普通)索引[比如用户经常用的某个普通字段,那么我们就可以用这个字段建立索引提高查询效率]。
对于 MyISAM ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复; 叶子data部分保存的都是真正记录的地址
而对于 INNODB,建立辅助(普通)索引,可以看到, InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值(用于下一步回表查询)。
所以INNODB通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键(innodb的索引特殊性,即便没有主键,也会默认生成),然后用主键到主索引中检 索获得记录。这种过程,就叫做回表查询;但不是innodb每次查询都需要回表:
有时候复合索引常用,比如(key1,key2)的索引,我们用key2索引查询key1的时候,这时候查询的字段在索引中已经能拿到了,就不需要回表查询了!
-- 方法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);
主键索引特点:
-- 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);
唯一索引特点:
-- 方法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 的索引
普通索引的特点:
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.
覆盖索引是一种数据查询方式;
在索引中,通过索引值可以直接找到要查询字段的值,而不需要通过主键值回表查询,那么就叫覆盖索引; ,
类似于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语句以及连接表的相关信息