1、存储结构
- 记录是按照行来存储的,可是数据库的读取并不以行为单位,不然一次读取(也就是一次 I/O 操做)只能处理一行数据,效率会很是低。
- 在数据库中,不论读一行,仍是读多行,都是将这些行所在的页进行加载。数据库管理存储空间的基本单位是页(Page)。
表空间 | Tablespace |
|
段 | Segment |
|
区 | Extent |
|
页 | Page |
|
行记录 | Row |
|
2、数据页内的结构
- 按类型划分:常见的有数据页(保存 B+ 树节点)、系统页、Undo 页和事务数据页等。数据页是咱们最常使用的页。
- 表页的大小限定了表行的最大长度,不一样 DBMS 的表页大小不一样
- 好比在 MySQL 的 InnoDB 存储引擎中,默认页的大小是 16KB。
show variables like '%innodb_page_size%';
- 数据库 I/O 操做的最小单位是页,与数据库相关的内容都会存储在页结构里
- 数据页包括七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum+supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)。
![]() |
![]() |
第一部分,文件通用部分,文件头和文件尾
- 文件头和文件尾,相似集装箱,将页的内容进行封装,经过文件头和文件尾校验的方式来确保页的传输是完整的
- 文件头中有两个字段,分别是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT,它们的做用至关于指针,分别指向上一个数据页和下一个数据页,一个双向的链表
- 数据页之间不须要是物理上的连续,而是逻辑上的连续
- 文件尾的校验方式就是采用 Hash 算法进行校验
- 经过文件尾的校验和(checksum 值)与文件头的校验和作比对,若是两个值不相等则证实页的传输有问题,须要从新进行传输,不然认为页的传输已经完成
第二部分,记录部分
- 页的主要做用是存储记录,“最小和最大记录”和“用户记录”部分占了页结构的主要空间
- 另外空闲空间是个灵活的部分,有新的记录插入时,会从空闲空间中进行分配用于存储新记录。
第三部分,索引部分
- 重点指的是页目录,起到了记录的索引做用
- 在页中,记录是以单向链表的形式进行存储的
- 单向链表的特色就是插入、删除很是方便,可是检索效率不高,最差的状况下须要遍历链表上的全部节点才能完成检索
- 在页目录中提供了二分查找的方式,用来提升记录的检索效率
- 将全部的记录分红几个组,包括最小记录和最大记录,但不包括标记为“已删除”的记录
- 第 1 组,最小记录所在的分组只有 1 个记录;最后一组(最大记录所在的分组)会有 1-8 条记录;其他的组记录数量在 4-8 条之间。除了第 1 组(最小记录所在组)之外,其他组的记录数会尽可能平分。
- 在每一个组中最后一条记录的头信息中会存储该组一共有多少条记录,做为 n_owned 字段。
- 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照前后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每一个槽至关于指针指向了不一样组的最后一个记录
上面的图示进行举例算法
- 5 个槽的编号分别为 0,1,2,3,4,查找主键为 9 的用户记录
- 初始化查找的槽的下限编号,设置为 low=0,设置查找的槽的上限编号 high=4
- 采用二分查找法进行查找,首先找到槽的中间位置 p=(low+high)/2=(0+4)/2=2
- 取编号为 2 的槽对应的分组记录中最大的记录,关键字为 8
- 由于 9 大于 8,因此会在槽编号为 (p,high]的范围进行查找
- 接着从新计算中间位置 p’=(p+high)/2=(2+4)/2=3
- 查找编号为 3 的槽对应的分组记录中最大的记录,关键字为 12
- 由于 9 小于 12,因此应该在槽 3 中进行查找
- 遍历槽 3 中的全部记录,找到关键字为 9 的记录,取出该条记录的信息,即为要查找的内容
页目录存储的是槽,槽至关于分组记录的索引,经过槽(二分查找)查找记录。sql
3、从数据页的角度看 B+ 树是如何进行查询的
- 叶子节点,B+ 树最底层的节点,节点的高度为 0,存储行记录
- 非叶子节点,节点的高度大于 0,存储索引键和页面指针,不存储行记录自己
- 一棵 B+ 树中,每一个节点都是一个页,每次新建节点的时候,就会申请一个页空间
- 同一层上的节点之间,经过页的结构构成一个双向的链表(页文件头中的两个指针字段)
- 非叶子节点,包括了多个索引行,每一个索引行里存储索引键和指向下一层页面的页面指针
- 最后是叶子节点,它存储了关键字和行记录,在节点内部(也就是页结构的内部)记录之间是一个单向的链表,可是对记录进行查找,则能够经过页目录采用二分查找的方式来进行
经过 B+ 树的索引查询行记录数据库
- 首先是从 B+ 树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止
- 将数据页加载到内存中
- 页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组
- 而后再在分组中经过链表遍历的方式查找记录。
普通索引和惟一索引,查询效率比较缓存
- 惟一索引就是在普通索引上增长了约束性,关键字惟一,找到了关键字就中止检索
- 普通索引,可能会存在用户记录中的关键字相同的状况
- 根据页结构的原理,当咱们读取一条记录的时候,不是单独将这条记录从磁盘中读出去,而是将这个记录所在的页加载到内存中进行读取
普通索引的字段上进行查找也就是比惟一索引在内存中多几回“判断下一条记录”的操做,检索效率上基本上没有差异服务器
4、缓冲池
- 磁盘 I/O 须要消耗的时间不少,在内存中进行操做效率则会高不少
- 为了提升性能,DBMS 会申请占用内存来做为数据缓冲池,让磁盘活动最小化,从而减小与磁盘直接进行 I/O 的时间,提高查询性能
- 若是索引的数据在缓冲池里,访问的成本就会下降不少
- 缓冲池管理器会尽可能将常用的数据保存起来,在数据库进行页面读操做的时候,首先会判断该页面是否在缓冲池中
- 若是存在就直接读取,若是不存在,就会经过内存或磁盘将页面存放到缓冲池中再进行读取
- 对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息
- 数据库会以必定的频率刷新到磁盘上
- 并非每次发生更新操做,都会马上进行磁盘回写
- 缓冲池会采用一种叫作 checkpoint 的机制将数据回写到磁盘上,提高数据库的总体性能
- 当缓冲池不够用时,须要释放掉一些不经常使用的页
- 能够采用强行采用 checkpoint 的方式,将不经常使用的脏页回写到磁盘上
- 再从缓冲池中将这些页释放掉【脏页(dirty page)指的是缓冲池中被修改过的页,与磁盘上的数据页不一致】
- MyISAM 存储引擎,只缓存索引,不缓存数据,对应的键缓存参数为 key_buffer_size。
- InnoDB 存储引擎,能够经过查看 innodb_buffer_pool_size 变量来查看缓冲池的大小。
show variables like 'innodb_buffer_pool_size'; -- 查看 set global innodb_buffer_pool_size = xxxx; -- 设置 show variables like 'innodb_buffer_pool_instances'; -- 查看缓冲池的个数若是想要开启多个缓冲池性能
- 首先须要将innodb_buffer_pool_size参数设置为大于等于 1GB,这时innodb_buffer_pool_instances才会大于 1
- 能够在 MySQL 的配置文件中对innodb_buffer_pool_size进行设置,大于等于 1GB
- 而后再针对innodb_buffer_pool_instances参数进行修改
缓冲池三种读取数据的方式
1. 内存读取,若是该数据存在于内存中,基本上执行时间在 1ms 左右,效率仍是很高的。spa
2. 随机读取指针
若是数据没有在内存中,就须要在磁盘上对该页进行查找,总体时间预估在 10ms 左右code
- 6ms 是磁盘的实际繁忙时间(包括了寻道和半圈旋转时间)
- 3ms 是对可能发生的排队时间的估计值
- 还有 1ms 的传输时间,将页从磁盘服务器缓冲区传输到数据库缓冲区中
10ms 看起来很快,但实际上对于数据库来讲消耗的时间已经很是长,由于只是一个页的读取时间对象
3. 顺序读取
一种批量读取的方式
- 咱们请求的数据在磁盘上每每都是相邻存储的,顺序读取能够帮咱们批量读取页面
- 一次性加载到缓冲池中就不须要再对其余页面单独进行磁盘 I/O 操做了
- 若是一个磁盘的吞吐量是 40MB/S,那么对于一个 16KB 大小的页来讲,一次能够顺序读取 2560(40MB/16KB)个页,至关于一个页的读取时间为 0.4ms
- 采用批量读取的方式,即便是从磁盘上进行读取,效率也比从内存中只单独读取一个页的效率要高
SQL 语句的查询成本
SHOW STATUS LIKE 'last_query_cost';
- 一条 SQL 查询语句在执行前须要肯定查询计划,若是存在多种查询计划的话,MySQL 会计算每一个查询计划所须要的成本,从中选择成本最小的一个做为最终执行的查询计划
- 在执行完这条 SQL 语句以后,经过查看当前会话中的 last_query_cost 变量值来获得当前查询的成本
- 这个查询成本对应的是 SQL 语句所须要读取的页的数量
- 位置决定效率。若是页就在数据库缓冲池中,那么效率是最高的,不然还须要从内存或者磁盘中进行读取,固然针对单个页的读取来讲,若是页存在于内存中,会比在磁盘中读取效率高不少
- 批量决定效率。若是咱们从磁盘中对单一页进行随机读,那么效率是很低的(差很少 10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提高不少,甚至要快于单个页面在内存中的随机读取
考虑数据存放的位置,若是是常用的数据就要尽可能放到缓冲池中,其次能够充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就获得了提高。
查询缓存
- 提早把查询结果缓存起来,这样下次就不须要执行能够直接拿到结果
- 在MySQL中的查询缓存,不是缓存查询计划,而是查询及对应的查询结果
- 查询匹配的鲁棒性大大下降,只有相同的查询操做才会命中查询缓存
缓存池与查询缓存,异同:(缓冲池不是查询缓存)
共同的特色:都是经过缓存的机制来提高效率。
差别:
缓冲池 | 服务于数据库总体的IO操做,经过创建缓冲池机制来弥补存储引擎的磁盘文件与内存访问之间的效率鸿沟,同时缓冲池会采用“预读”的机器提早加载一些立刻会用到的数据,以提高总体的数据库性能 |
查询缓存 | 服务于SQL查询和查询结果集的,由于命中条件苛刻,并且只要当数据表发生了变化,查询缓存就会失效,命中率低 |