一、认识索引
1.1 索引的概念
数据库表中存储的数据都是以记录为单位的,若在查询数据时直接一条条遍历表中的数据记录,查询的时间复杂度为O(N)
索引的价值在于提高海量数据的检索速度,只要执行了正确的创建索引的操作,查询速度就可能提高成百上千倍。当一张表创建索引后,在数据库底层就会为表中的数据记录构建特定的数据结构,后续在查询表中数据时就能通过查询该数据结构快速定位到目标数据
索引虽然提高了数据的查询速度,但在一定程度上也会降低数据增删改的效率,因为这时在对表中的数据进行增删改操作时,除了需要进行对应的增删改操作之外,可能还需要对底层建立的数据结构进行调整维护
常见的索引分为:
主键索引(primary key)
唯一索引(unique)
普通索引(index)
全文索引(fulltext)
1.2 索引的价值
使用如下SQL创建一个海量数据表
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中创建了一个名为index_demon的数据库,在该数据库中创建了一个名为EMP的员工表,并向表中插入了八百万条记录
将上述SQL保存到文件中,然后在MySQL中使用source命令依次执行文件中的SQL即可
SQL执行完毕后查看数据库即可看到一个名为index_demon的数据库。进入该数据库,在数据库中可以看到一个名为EMP的员工表
通过desc命令可以发现,目前EMP员工表中没有建立任何索引
指定员工号查询EMP表中的员工信息,每次都需要大概4.8秒的时间
当给员工表中的工号建立索引后,数据库底层就会为员工表中的数据记录构建特定的数据结构。但由于当前员工表中的数据量较大,因此建立索引时也需花费较长时间
此时再查询EMP表中指定工号的员工信息,可以看到几乎检测不到查询时耗费的时间
给员工工号创建索引后再根据员工工号查询数据,此时就可直接通过底层建立的数据结构来快速定位到目标数据,从而提高数据的检索速度
二、认识磁盘
MySQL给用户提供存储服务,存储的数据在外设磁盘中
磁盘是计算机中的一个机械设备,相比于计算机的其他电子元件(内存),磁盘IO的效率是比较低的
2.1 磁盘结构
永磁铁: 机械硬盘的存储方式与磁带比较类似,磁体具有记忆的功能,永磁铁用于保证磁性的稳定
音圈马达: 硬盘读取数据的关键部位,主要作用是将存储在磁盘上的信息转换为电信号向外传输
主轴: 保证电机稳定的转动,磁盘转动才能读取数据
空气滤波片: 过滤空气硬盘透气孔中进入的空气,保证硬盘内部清洁,同时还可防止硬盘内部的零件氧化,确保硬盘安全使用
磁盘: 一般由铝合金制作,主要是用来存储文件的
磁头: 用来读取盘片上的信息
串行接口: 用来连接电脑与硬盘的接口,起到传输的作用
盘片
一个磁盘由多个盘片叠加而成,盘片的表面涂有磁性物质,磁性物质用于记录二进制数据。盘片的正反两面都可涂上磁性物质,因此一个盘片有两个盘面
磁道: 磁盘表面被分为许多同心圆,每个同心圆称为一个磁道,每个磁道都有一个编号,最外面的是0磁道
扇区: 每个磁道被划分成若干个扇区,每个扇区的存储容量为512字节,每个扇区都有一个编号
由于每个扇区的存储容量相同,因此最内侧磁道上的扇区数据密度最大,而最外侧磁道上的扇区数据密度最小。近三十年来,扇区大小一直是512字节,但最近几年正在迁移到更大、更高效的4096字节扇区,通常称为4K扇区。数据库文件就是保存在磁盘中的一个个扇区中的,因此找到一个文件本质,即在磁盘上找到保存该文件的所有扇区
扇区的定位方式
磁头(Heads): 每个盘面都有一个对应的磁头,因此确定了磁头也就确定了数据在哪一个盘面
柱面(Cylinder): 所有盘面中半径相同的同心磁道构成柱面,在确定了数据在哪一个盘面的基础上,再确定柱面也就确定了数据在该盘面上的哪一个磁道
扇区(Sector): 每个磁道被划分成若干个扇区,因此在确定了数据在哪一个磁道的基础上,再确定扇区也就确定了数据在该磁道上的哪个扇区
定位扇区时采用CHS寻址方式,即先通过H确定数据所在的盘面,再通过C确定数据所在的磁道,最后通过S定位到目标扇区
CHS寻址方式是磁盘定位扇区的方式,但实际CHS寻址方式对磁盘以外的设备来说没什么作用,因此系统软件在定位磁盘上的数据时采用的是LBA(Logical Block Address,逻辑区块地址)
LBA是描述计算机存储设备上数据所在区块的通用机制,LBA和CHS之间可以通过计算公式进行相互转换,LBA存在的意义就是对底层逻辑器件进行虚拟化,让系统软件可以不用关心底层硬件具体的寻址方式,而实际底层硬件采用的还是CHS寻址方式
2.2 操作系统与磁盘交互的基本单位
操作系统与磁盘进行IO交互的基本单位是4KB,而不是扇区的大小512字节,原因如下:
物理内存实际是被划分成一个个4KB大小的页框的,磁盘上的数据也会被划分成一个个4KB大小的页帧,因此操作系统与磁盘以4KB为单位进行IO交互,就能提高数据加载和保存的效率
操作系统与磁盘进行IO交互时,若直接以扇区的大小作为IO的基本单位,那么这时系统的IO代码和硬件就是强相关的,将来当硬件的扇区大小发生变化时就需要对应修改操作系统的IO代码
以扇区的大小作为IO的基本单位太小了,意味着读取同样的数据内容,需要更多次的磁盘访问,而磁盘IO的效率较低,整体IO效率就降低了
因此操作系统与磁盘以4KB作为IO交互的基本单位,一方面是为了提高IO效率,另一方面是为了实现硬件和系统的解耦
2.3 磁盘的随机访问(Random Access)与连续访问(Sequential Access)
随机访问: 本次IO所给出的扇区地址与上次IO给出的扇区地址不连续,磁头在两次IO操作之间需做比较大的移动动作才能找到目标扇区
连续访问: 本次IO所给出的扇区地址与上次IO给出的扇区地址是连续的,磁头很快就能找到目标扇区进行IO
若两次IO是在同一时刻发出的,但其请求的扇区地址相差很大,那也只能称为随机访问,因为连续访问中的连续指的是访问的扇区地址的连续,而不是访问时间的连续,由于连续访问不需要过多的定位,因此效率较高
2.4 MySQL与磁盘交互
MySQL作为一款应用软件,可以想象成是一种特殊的文件系统,其有着更高频的IO场景,因此为了提高基本的IO效率,MySQL与磁盘交互的基本单位是16KB,这个基本数据单元在MySQL中也叫作Page
通过show命令查看系统中的全局变量,可以看到InnoDB存储引擎交互的基本单位是16KB
说明:本篇博客中没有做特殊说明的地方,都以InnoDB存储引擎为例进行讲解
Buffer Pool
在MySQL中进行的各种CRUD操作时,都需先通过计算找到对应的操作位置,只要涉及计算就需CPU参与,而冯诺依曼体系结构决定了CPU只能与内存交互,因此就需先将数据加载到内存中
在特定的时间内,MySQL中的数据一定是同时存在于磁盘和内存中的,当操作完内存数据后,再以特定的刷新策略将内存中的数据刷新到磁盘中,这时MySQL和磁盘进行数据交互的基本单位就是Page
为了更好的支持上述操作,MySQL服务器在启动的时候会预先申请一块内存空间来进行各种缓存,这块内存空间即Buffer Pool,后续磁盘中加载的数据就会保存在Buffer Pool中,刷新数据即将Buffer Pool中的数据刷新到磁盘
由于内核中是有内核缓冲区的,因此MySQL从磁盘读取数据时,需先将数据从磁盘读取到内核缓冲区,再将数据从内核缓冲区读取到Buffer Pool,MySQL将数据刷新到磁盘时,同样需先将数据从Buffer Pool刷新到内核缓冲区,再将数据从内核缓冲区刷新到磁盘
因此所谓的操作系统和磁盘交互的基本单位是4KB,是指内核缓冲区与磁盘之间是以4KB为单位进行交互的。而MySQL的Buffer Pool和磁盘实际并不是直接交互的,所谓的MySQL与磁盘交互的基本单位是16KB,指的是MySQL的Buffer Pool与内核缓冲区之间是以16KB为单位进行交互的。只不过说的时候更关注的是MySQL和磁盘之间的关系,所以直接说MySQL与磁盘交互的基本单位是16KB,相当于忽略了中间的内核缓冲区
为什么MySQL与磁盘交互的基本单位是Page
MySQL与磁盘进行交互时为什么不是按需交互,而是以Page为基本单位进行交互的?
当查询表中的某一条记录时,若MySQL只从磁盘中将这一条记录加载到内存中,那么当继续查询表中的其他记录时,MySQL就一定需要再次与磁盘进行IO交互
而若查询表中的某一条记录时,MySQL直接将这条记录所在的整个Page都加载到内存中,那么继续查询表中的其他记录时,MySQL很可能就不再需要与磁盘进行IO交互了,因为这条记录很可能也在被加载进来的Page中,直接在内存中进行查询即可,减少了IO的次数
但不能保证用户下一次要访问的数据一定就在本次加载进来的Page中,但是根据统计学原理,当一个数据正在被访问时,那么下一次有很大可能会访问其周围的数据(局部性原理),因此有较大概率保证用户下一次要访问的数据和本次访问的数据在同一个Page中,若局部性原理没有起作用,那就再把对应的Page加载到内存中即可
即,MySQL与磁盘进行交互时以Page为基本单位,可以减少与磁盘IO交互的次数,进而提高IO的效率