目录
一、索引的概念及作用
二、实际看看索引的效率提升
三、认识磁盘
1. 简单了解磁盘
2. 数据库文件存储位置
3. 定位扇区
4. 数据读取效率问题
5. 磁盘随机访问与磁盘连续访问
5.1 随机访问
5.2 连续访问
四、mysql与磁盘的交互
五、建立共识
一、索引的概念及作用
索引,其实就是用于提高数据库的性能的。使用它不用加内存、不用改程序、不用调sql,只需要执行正确的“create index”,就可以让数据库的查询速度提高成百上千倍。
当然,数据库查询效率的提高也是有代价,那就是在插入、更新、删除的时候会增加大量的IO,拉低效率。因此,索引的价值仅仅体现在提高海量数据的检索速度上。
常见的索引一般分为如下几种:
主键索引(primary key)、唯一索引(unique)、普通索引(index)和全文索引(fulltext)。
注意,mysql的服务器是在内存中的,因此,当mysql启动后,它会在内存中为我们开辟一块空间,当需要修改数据时,就会将磁盘中的数据加载到这块内存中,在内存中执行CURD操作,然后在合适的时间将内存中的数据刷新到外设中。索引也是如此。
大家知道,在实际中如果我们要提高搜索效率,其实就是对搜索的算法进行优化。对算法优化一般包含两个方面,一个是数据结构本身,例如将线性结构换成二叉树结构。第二种就是修改算法本身,即优化在特定数据结构下的查找方法,例如在线性表中不再线性遍历,而是二分查找。一般而言,数据结构改变,算法本身也需要跟着改变。
对于索引而言也是如此。在索引中就是将数据存储到了特定的数据结构中,利用这个数据结构的优势提高搜索效率。因此,所谓的索引,其实就是在一个特定的数据结构中搜素数据。
二、实际看看索引的效率提升
为了方便看到索引带来的效果,所以我们需要准备一份非常大的数据进行索引。
在这里准备了这样的一张表,然后向里面插入800w行数据。如果大家也想在自己的机器上实际看到索引的效果,可以到网上直接搜索存储大量数据的数据表,有现成的代码,大家直接复制就可以向特定表中插入大量的数据。
注意,当插入成百上千万行数据时,根据你的配置,插入需要的时间可能不同,配置好的话可能几分钟,配置差点可能就要10几分钟乃至更高。大家看到mysql一直下面的插入状态的话,不要去结束它。
还有一个点,在你复制的代码中检查一下数据表的创建里面有没有带上主键、唯一键等属性,如果有,将它去除掉,以方便看到一份“没有索引”的表的搜索效率。
当数据插入完成后,千万不要直接输入“select * from 数据表”查看数据表内容,因为里面的数据量非常大,如果直接用该命令而不带筛选选项,mysql就会不断显示数据。
有了这份存储有大量数据的表后,我们搜索一份数据:
可以看到,在这份存储有800w行数据的表内查询一个数据,花费了5.65s。很明显,这是不能接受的。要知道,一个公司的数据库中有个几百上千万行数据是非常正常的,如果一个用户查询一条数据,数据库要用5s才能返回数据,很明显是用户所不能接受。并且这仅仅是一个用户查,如果是成百上千乃至更多用户同时查数据,那么数据库的响应速度会更慢,甚至直接挂掉。
由此,我们就必须要想办法提高数据库中数据的查询效率。
输入“alter table 数据库名 add index(列名)”,指定一列为其添加索引。
索引添加完后,我们再到数据库中查询同一份数据:
可以看到,当我们为表添加索引后再去表中搜索数据,它的搜索时间就变为了0s。几乎就是在一瞬间就搜索完成了。通过这个例子,就可以明显的看到索引带来的查询效率提升。
三、认识磁盘
1. 简单了解磁盘
我们知道,一般来讲,数据都是存储在磁盘中的。因此,mysql作为一个给用户提供数据存储服务的服务端,它也是将用户数据存储到磁盘这个外设中的。但我们知道,磁盘作为计算机中的一个机械设备,相比与计算机中的其他电子元件,磁盘的存储效率是比较低的。再加上数据IO本身还有一定的效率消耗,如何提升mysql的效率就是一个重要问题了。
由于本章的重点并不是研究硬件,并且在我以前关于linux的文章中也介绍过磁盘的结构了,这里就简要介绍一下。
一块普通的磁盘,它的物理结构如下所示:
在这里面的圆盘,就是盘片,上面是有起伏的,每个起伏就代表着二进制,我们的数据就是存储在这块盘片中的。在盘片的上下有一个磁头,它会左右来回摆动,磁头的作用就是读取盘片中保存的二进制数据。在盘片的中间有一个主轴,这个主轴中有一个马达,用于让盘片高速旋转。
注意,磁盘上的盘片是一摞,而不是一片。在这些盘片的上下两面都可以存储数据,这就意味着每一面都有一个磁头在来回摆动读取数据。
在盘片中,磁盘表面被分为许多个同心圆,每个同心圆都称为一个磁道,每个磁道都有一个编号,最外面的就是0号磁道。
每个磁道又被划分为若干段(段又叫扇区)。每个扇区的存储容量都是相同的,一般为512字节,每个扇区都有一个编号。但是随着磁盘的发展,现在的扇区正在逐渐扩大,已经出现了更高效的4096字节扇区,被称为“4K扇区”。当然,因为我们不是专门研究磁盘的,不必太过关心,有个大致了解即可。
注意,虽然磁盘上每个扇区的实际大小有差距,但是上面的存储容量都是一样的,因为每个扇区的存储数据量不是看扇区大小,而是看数据存储密度。虽然上文中说磁盘上每个扇区的大小是一样的,但这并不绝对,随着磁盘的发展,已经慢慢出现了扇区容量不一样的磁盘了。在这里暂时不做考虑。
2. 数据库文件存储位置
在数据库中的数据库文件,无论是数据库,还是表,还是表中的数据,其本质都是被保存在磁盘的盘片中的一个个扇区内的。当然,由于一个扇区的存储容量很小,所以当数据库文件很大是,就需要占据多个扇区。
就算是我们当前使用的linux中所看到的大部分目录或文件,其实也是保存在硬盘中的。(注意,有一些内存文件系统,如porc、sys等,我们不做考虑)。
由此,找到一个文件,本质就是在磁盘上找到保存该文件的扇区。这也就要求磁盘要有能够定位任意一个扇区的能力。
3. 定位扇区
上图是一张有三个磁盘六个盘面的磁盘。在这个磁盘中,每一面都有一个对应的磁头用于读取数据。当我们需要查找某份数据时,就是先确定它在哪一面,找到数据所处的盘面后,再找到它所在磁道, 然后找到数据在该磁道上的哪一个扇区。当找到数据所在扇区后,就可以通过磁头读取数据。
由此,我们只需要知道磁头、柱面(等价于磁道)、扇区对应的编号,就可以在磁盘上定位所要访问的扇区,这种磁盘数据定位方式叫做CHS。不过实际系统软件中使用的不是CHS,而是LBA,是一种线性地址,大家可以LBA和CHS分别想象为虚拟地址和物理地址。系统会将LBA地址转化为CHS,交给磁盘去进行数据读取。
4. 数据读取效率问题
通过上面的内容大家应该就知道磁盘是可以定义任意一个扇区的。但是,在系统层面上来看,难道系统软件就是直接按一个扇区(512字节或4096字节)进行IO交互的吗?其实并不是。
这里存在几个原因。
(1)如果OS直接使用硬件提供的数据大小进行交互,这就意味着系统的IO代码和硬件强相关,当硬件发生变化时,系统的IO也就必须跟着变化。
(2)从实际来看,以512字节进行IO还是太小了。IO单位过小,就意味着读取同样的数据内容,需要进行更多的磁盘访问,会带来效率的降低。
(3)大家应该了解过文件系统,文件系统中读取数据的基本单位就不是扇区,而是数据块。一个数据块的大小是4KB。
因此,系统读取磁盘时,并不是以扇区为单位,而是以数据块为单位,基本单位是4KB。
至于为什么一个数据块是4KB,一个方面是内存管理的问题。磁盘数据是需要加载到内存中的,而内存中本身就是以4KB划分成多个数据块的,因此,以数据块为基本单位与磁盘交互,更有利于磁盘与内存的IO交互。
另一个理由就是效率问题。在磁盘读取中,就算系统只需要1字节的数据,也需要从磁盘中读取4KB,有人可能认为这是一种浪费,但是这是从效率上考虑过的。因为系统读取数据时,它读取的数据大概率都是存放在一起的,而一次性读取4KB数据,就可以做到数据的预加载,当系统再次读取数据时,它读取的数据大概率就是已经提前加载好的数据,以此减少系统与磁盘的IO次数,提高数据的读取效率。
5. 磁盘随机访问与磁盘连续访问
5.1 随机访问
随机访问,是指本次IO所给出的扇区地址和上次IO给出的扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读写数据。
5.2 连续访问
如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能够很快的开始这次IO操作。这样的多个IO操作称为连续访问。
注意,哪怕相邻的两次IO操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话,也只能称为随机访问,而非连续访问。
因为连续访问的效率要比随机访问高,所以大家涉及IO的程序中,尽量还是要体现出连续访问,而非随机访问。但是要注意,磁盘的效率问题并不是这么简单就能够解决的,磁盘中也是有很多方案设计用于提高效率的。这里只是简单介绍一下。
四、mysql与磁盘的交互
mysql作为一个应用软件,可以想象为一种特殊的文件系统。它有着更高的IO需求。因此,为了提高基本的IO效率,mysql进行IO的基本单位是16KB。
这也就意味着,虽然磁盘的基本单位是512字节,但是MYSQL中的InnoDB引擎使用16KB进行IO交互。即mysql和磁盘进行数据交互的基本单位是16KB。这个基本数据单元,在mysql中叫做“page”(注意,这个page和系统的page不同,要将两者区分开来)。
但是大家知道,在OS中,应用软件是无法直接与外设交互的,必须通过OS。因此,当mysql要与磁盘IO时,并不是双方直接交互,而是OS从磁盘中读取4个数据块,即16KB的数据放到文件系统的文件缓冲区内,而在文件系统中的一个文件缓冲区,其实就可以看成是文件系统打开的一个文件,这个文件会返回一个文件描述符给mysql,让mysql用这个文件描述符读取数据。当然,这并不准确。在mysql中也是存在一个自己的“buffer pool”的,大家可以将其看做一个存储数据的空间。mysql所有的CURD操作都是在这个buffer pool中完成的。
至于mysql中写入的数据,就是交给OS,然后OS在满足一定条件后将数据刷新到磁盘上即可。通过这种方式,就提高了mysql的IO效率。
大家也可以在自己的linux中的mysql下输入“show global status like 'innodb_page_size';”命令查看使用InnoDB引擎的mysql下的page大小:
可以看到,大小为16384,单位是字节,换算过来就是16KB。
五、建立共识
通过上面的内容,我们就需要建立以下几个共识:
(1)mysql中的数据文件,是以page为单位保存在磁盘当中的。
(2)mysql的CURD操作,都需要通过计算找到对应的插入位置,或者找到对应要修或查询的数据。
(3)在计算机中只要涉及到计算,就需要CPU参与,而为了方便CPU参与,就需要将数据移动到内存中。
(4)因为要将数据移动到内存中,这就意味着在特定时间段内,数据一定是磁盘和内存中都有的。后续操作完内存数据之后,就需要以特定的刷新策略将数据刷新到磁盘中。此时,就涉及到磁盘和内存的数据交互,也就是IO了。而mysql中IO的基本单位就是page。
(5)为了能够更好的进行上面的操作,mysql服务器在内存中运行的时候,在服务器内部就申请了被称为“buffer pool”的大内存空间,这个空间默认为128MB,用于和磁盘数据进行IO交互。
(6)为了提高效率,一定要尽可能的减少系统和磁盘IO的次数。
上文中说mysql中有一个buffer pool内存空间,我们可以输入“vim /etc/my.cnf”打开数据库配置文件查看:
可以看到,配置文件中有这么一条注释,将这条指令放开,我们就可以修改buffer pool的大小。不放开的话大小默认为128MB。