构建一张具有大量数据的表
drop database if exists `index_demon`;
create database if not exists `index_demon` default character set utf8;
use `index_demon`;
-- 构建一个8000000条记录的数据
-- 构建的海量表数据需要有差异性,所以使用存储过程来创建
-- 产生随机字符串
delimiter $$
create function rand_string(n INT)
returns varchar(255)
begin
declare chars_str varchar(100) default
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
declare return_str varchar(255) default '';
declare i int default 0;
while i < n do
set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i = i + 1;
end while;
return return_str;
end $$
delimiter ;
-- 产生随机数字
delimiter $$
create function rand_num( )
returns int(5)
begin
declare i int default 0;
set i = floor(10+rand()*500);
return i;
end $$
delimiter ;
-- 创建存储过程,向雇员表添加海量数据
delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i + 1;
insert into EMP values ((start+i)
,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
until i = max_num
end repeat;
commit;
end $$
delimiter ;
-- 雇员表
CREATE TABLE `EMP` (
`empno` int(6) unsigned zerofill NOT NULL COMMENT '雇员编号',
`ename` varchar(10) DEFAULT NULL COMMENT '雇员姓名',
`job` varchar(9) DEFAULT NULL COMMENT '雇员职位',
`mgr` int(4) unsigned zerofill DEFAULT NULL COMMENT '雇员领导编号',
`hiredate` datetime DEFAULT NULL COMMENT '雇佣时间',
`sal` decimal(7,2) DEFAULT NULL COMMENT '工资月薪',
`comm` decimal(7,2) DEFAULT NULL COMMENT '奖金',
`deptno` int(2) unsigned zerofill DEFAULT NULL COMMENT '部门编号'
);
-- 执行存储过程,添加8000000条记录
call insert_emp(100001, 8000000);
将代码放入 名字.sql 文件中 在mysql使用source命令导入,就会构建表
desc查看表结构,可以看到表既没有主键(此处先认为主键会影响索引),也没有任何索引。
查询EMP表中指定工号的员工信息,可以看到每次查询过程都需要花费4.5秒左右。如下:
当我们给员工表中的工号建立索引后,数据库底层就会为员工表中的数据记录构建特定的数据结构,需要注意的是,由于当前员工表中的数据量较大,因此建立索引时也需要花费较长时间。如下:
这时再查询EMP表中指定工号的员工信息,可以看到几乎检测不到查询时耗费的时间。如下:
根本原因就是,给员工工号创建索引后再根据员工工号来查询数据,这时就能够直接通过底层建立的数据结构来快速定位到目标数据,从而提高数据的检索速度,这就是索引的价值。
下面让我们学习索引。
通过之前的学习,我们知道,mysql创建数据库其实就是创建一个目录,在innodb存储引擎下,创建一张表其实就是创建两个文件,在linux下,一切皆文件,此时,mysql进行数据持久化存储则为对磁盘文件系统操作。
磁盘及文件系统(点击跳转)
MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率, MySQL 进行IO的基本单位是 16KB (后面统一使用 InnoDB 存储引擎讲解)
1、MySQL服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。Buffer Pool单次向磁盘刷入1M数据和单次向磁盘刷入100M数据,效率肯定是不同的,MySQL底层也有自己的‘缓冲区’刷新策略来保证IO效率。
2、MySQL中的数据文件,是以page为单位保存在磁盘当中的。
3、MySQL的CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。
4、而只要涉及计算,就需要CPU参与,为了便于CPU参与,一定要先将数据移动到内存当中。所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。
5、为了更高的效率,一定要尽可能的减少系统和磁盘IO的次数。
磁盘这个硬件设备的基本单位是 512 字节(有些会更大),而 MySQL InnoDB引擎 和磁盘进行数据交互的基本单位是 16KB,这一个个的基本数据单位,在 MySQL 中叫做page(注意和系统的page区分)
MySQL中必然存在大量的page,为了先描述再组织,单个page内部除了用户数据之外,还存在一部分的数据结构,用于MySQL组织管理大量的page。
使用page进行IO交互有什么好处呢?
预加载,根据局部性原理,减少IO次数,提升效率。
IO效率低下的最主要矛盾不是IO单次数据量的大小,而是IO的次数。
这目录就像看书、查字典一样,我们可以通过目录快速定位目标数据的大致页数,提升查找效率。所以MySQL的page里的这些目录虽然说多占用了一丢丢的空间,但大大提高了我们查找数据的速度(空间换时间)
这也解释本段开头我们无序插入数据时,MySQL自动帮我们进行了排序。这是因为只有数据是有序的,MySQL才方便引入页目录,提升后续的查找效率。
刚刚我们在每个page内部搞了页目录,减少了page内部的检索次数,提升了单page的搜索效率。
上图画出了多个page之间的连接关系,从图中看出页目录在多个page中也是呈现顺序关系的,如果是跨页搜索数据,也只能从前往后顺序遍历每个页的页目录,如果page一多,这种检索方式会大大降低页与页之间数据搜索速度,为了解决该问题,我们同样使用目录的方式对每个页中的目录进行管理:
上图同样使用了page的方式来管理页目录,每个page中不包含有效数据,只包含对应page中的始末页目录,所以一个“一级目录”可以管理上千个page。
那么问题又来了,如果底层的page很多,势必会造成一级目录的数量变多,那么我们对一级目录的遍历不是又变成了线性遍历了吗?再套一层:
一般两三层就够了,那些只用于索引的page,每个可都是16KB,单个page可以索引上千个下一层的page!完全够用,这样套了三层,即便面对海量数据,MySQL的查询效率也不低。(如果不够那就再套一层,存储数量指数级增长)后续查找时,自上而下进行查找,查到哪里就加载哪一部分的page,并不会加载整颗B+树到内存。
我们可以看出存储的数据结构是一颗b+树,要注意的是
1、并不是所有的存储引擎的索引都是采用B+树,还有哈希索引等方式。只能说主流的存储引擎是采用B+树作为索引的数据结构。
2、只有叶子结点采用链表进行级联,这是因为这是B+树的特性;同时,叶子结点进行级联可以满足范围查找(有时候数据读取的时候跨页了,叶子结点有指向next页的指针,就很方便)
1、非叶节点不保存数据,只用来索引,所有数据都保存在叶子节点。
2、数据只在叶子结点保存,并保存指向前后叶子结点的指针,通过链表指针对叶子结点进行级联,且叶子结点本身依关键字的自小而大顺序连接。
为什么要选择B+树
①只有叶子节点用来存储数据,其余节点用来存储目录,这样可以存储更多的目录。这样的一棵树,一定是比较“矮”的树,
这样在查询数据时,可以更快的找到数据。
②在查询数据时,一般局部性原理会吧要查询数据周围一定范围的数据也一并加载,使用链表方式,方便范围查找。
我们指明看主键列,MySQL会根据主键进行排序。如果我们在创建表的时候没有指明主键,MySQL会生成一个隐藏的列作为主键。这也说明了我们指明主键,MySQL会按照主键进行排序,我们不指明主键,MySQL的排序以默认生成的主键为准,所以这时我们的数据插入时的顺序是怎样的,拿出来的顺序就是怎样。
建立一张测试表
create table if not exists user (
id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引
age int not null,
name varchar(16) not null
);
插入数据
insert into user values(3,18,'孙权');
insert into user values(4,18,'刘备');
insert into user values(2,18,'关羽');
insert into user values(5,10,'张飞');
insert into user values(1,100,'周瑜');
MyISAM 引擎同样使用B+树作为索引结果,和上面讲的innodb存储引擎不一样的是,MyISAM叶节点的data域存放的是数据记录的地址。下图为 MyISAM表的主索引, Col1 为主键。
聚簇索引:像innodb存储引擎那样把B+树和数据存放在一起称为聚簇索引。
非聚簇索引:像MyISAM存储引擎那样把B+树和数据分离的方式称为非聚簇索引。
MyISAM的辅助(普通)索引
MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引。
对于 MyISAM ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。
MyISAM存储引擎可以在一张表中建立多个索引,下图就是基于 MyISAM 的 Col2 建立的索引,和主键索引没有差别:
innodb的辅助(普通)索引
InnoDB 除了主键索引,用户也会建立辅助(普通)索引,我们以上表中的 Col3 建立对应的辅助索引:
InnoDB的非主键索引中叶子节点并没有数据,而只有对应记录的key值。所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询。
为何InnoDB针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?数据主键索引有,没必要保存两份,不然太浪费空间了。
我们在前面学习中可知,在创建表时为表添加主键,其实也是在创建索引。
创建索引
②创建表的最后,指定某列或某几列为主键索引(和创建表时添加同理)。
③创建表后,使用alter命令给指定字段添加主键索引
创建索引可以分为很多,例如主键索引,唯一键索引,普通索引等。
主键索引的特点:
唯一键索引的特点:
普通索引的特点:
①使用show keys(index) from 表名SQL查询,比如查询articles表中的索引信息。如下:
②使用desc 表名SQL查询(信息比较简略),比如查询articles表中的索引信息。如下:
创建一个用户表用于测试索引的删除,表中包含用户的id、姓名和邮箱,并将这三列分别设置为主键索引、唯一索引和普通索引。如下:
删除主键索引
使用alter table 表名 drop primary keySQL即可删除主键索引。如下:
删除非主键索引
使用alter table 表名 drop index 索引名SQL即可删除指定的非主键索引。如下:
此外,也可以使用drop index 索引名 on 表名SQL也可以删除指定的非主键索引。如下
一个表只有一个主键索引,所以在删除主键索引的时候不用指明索引名,而一个表中可能有多个非主键索引,所以在删除非主键索引时需要指明索引名。
时刻要记住,创建索引的目的就是为了提高查询的效率。