研发人员必须了解的MySQL知识01

1. 概述

  • 对于研发人员来讲,主要关注mysql的索引、SQL优化、事务、锁、分库分表,参数调优一般是DBA干的活。

2. MySQL的架构

3. 存储引擎

3.1 概述

  • 研究记录怎么存储,怎么组织数据。

3.2 Page页

  • mysql的数据存在哪里?mysql的数据存储在磁盘中。数据是存储在一个个的Page里面的,Page是mysql的一个存储单元,是内外存交换的基本单元,每个Page大小16k。Page可以当做是一个大的数据结构。Page是加载到内存中使用的,mysql数据加载的单位是Page。下面是一个Page的示意图。
    在这里插入图片描述

  • Page Header:页头。记录一些统计信息,56字节。包含页的左右兄弟指针、页空间使用情况等。所有的Page构成一个双向链表。
    在这里插入图片描述

  • 虚记录:一个Page中存储的记录在最小虚记录和最大虚记录之间。
    最大虚记录:比页内最大主键还大;最小虚记录:比页内最小主键还小;

  • 记录堆:存放记录。分为有效记录和已删除记录两种。所有被删除的记录(上图蓝色)会被管理起来,被删除的记录被自由空间链表串起来。自由空间链表就是已删除记录构成的链表。

  • 未分配空间:Page中未使用的存储空间,插入新记录就往里面放。开始时一个Page都是未分配空间没有记录堆,随着记录的写入,不断产生记录堆。

  • Slot区:(上图白色部分)和mysql页内查找有关。(见下文【页内查找】)

  • Page Tailer:尾页。页面的最后部分,主要存储页面的校验信息,8字节。

3.2 页内记录维护

3.2.1 innodb索引的数据结构

  • innodb索引数据结构简图1
    在这里插入图片描述

  • innodb索引数据结构简图2
    在这里插入图片描述
    注意数据页(叶子节点)和索引页(非叶子节点)都是双向链表。

  • innodb索引叶子节点数据结构简图

    在这里插入图片描述
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVRlNwVw-1645261083092)(mysql01.assets/image-20220219152954077.png)]

  • 页内查找简图
    在这里插入图片描述
    页与页之间是双向链表,页内是单向链表

3.2.2 顺序保证

  • 问题:数据的存储是按照主键顺序。那么这个顺序是如何保证的?物理有序?逻辑有序?
  • 什么是物理有序:每个相邻元素物理地址连续。例如数组。优点是查询快,例如使用二分查找。缺点是增删慢,例如新增元素需要移动后面所有元素。
  • 什么是逻辑有序:元素之间内存地址不连续。例如链表。增删快;查询慢,只能遍历。
  • 显然mysql是逻辑有序。那么mysql的查询只能遍历吗?肯定不是,那么mysql的解决方案是什么?见【页内查找】

3.2.3 插入策略

  • 研究的是如何高效利用Page空间。
  • 优先使用自由空间链表,再使用未分配空间。 插入数据时先找自由空间链表,如果自由空间链表有节点,就将数据存进去;如果自由空间链表没有节点,就在未分配空间中申请一块区域将数据存进去。
  • 记录有可能放不进去。例如自由空间链表最大的节点是1k,但是新插入的这条数据2k。如果自由空间链表中节点存不下欲插入的数据,就在未分配空间中申请一块空间将数据存储进去。
  • 有可能存在磁盘空洞(磁盘碎片)。例如自由空间链表最小的节点是1k,但是新插入的这条数据是0.9k,那么剩下的0.1k空间就几乎不会被用到了。
  • 所以说,数据库用久了总会存在磁盘碎片,需要整理磁盘,如果不整理,数据库性能就会下降。怎么整理:一主一从,删除从库,从库重新从主库拉数据,从库拉完之后删除主库,主库再从从库中拉取数据(很操蛋但是有效的方案)。

3.2.4 页内查找

  • 上面说过,页之间双向链表,页内单向链表。

  • 链表只能遍历,但是作为数据库,查询不能只遍历,尤其针对读多写少的业务,mysql对于查找有一套优化方案。优化方案关键在于Slot区域,使用的数据结构是跳表。使用的查询算法是二分查找+遍历
    在这里插入图片描述

    • 红框部分是Slot区,这是一块连续的空间,一个Slot是8字节。每个slot指向链表中的一个节点,第一个slot指向链表头,也就是最小虚记录,最后一个slot指向链表尾,也就是最大虚记录。这样相当于将Page链表拆分成多个子链表。查询记录先在slot中做二分查找,找到slot之后再在子链表中做遍历。上图整体数据结构叫做跳表。

3.3 InnoDB存储引擎的磁盘、内存管理涉及到的技术点

3.3.1 概述

  • innodb的磁盘内存管理涉及到以下三方面技术

    • 预分配内存(Buffer Pool 预分配内存池)
      • Page保存在磁盘,使用必须先加载到内存。在c语言中,使用内存先申请。在数据库中不能这么做,效率低,因此会预先申请一块内存空间以备存储磁盘中加载的Page。
    • 数据加载单位(Page是Buffer Pool的最小单位,也是页面加载的最小单位)
      • 以Page为单位加载磁盘数据。即一次io加载一个Page,一次加载16k。
      • 一个Page中有一批记录,一次加载一个Page是为了提高io效率。加载一条记录,如果这条记录是热数据,那么这条记录两边的记录也可能是热数据,那么通过Page一次性加载到内存就提高了io效率。
    • 数据内外存交换
      • 内存空间小于磁盘空间,如果内存中已经加载满Page,那么想要加载新的Page如何是好?这就涉及到innodb的内存管理,内存和磁盘的数据交换策略。
  • 数据内外存交换示意图
    在这里插入图片描述

    • 涉及术语
      • Free list:空闲Page组成的链表。
      • Flush list:脏页列表。直译为刷盘列表。
      • Page hash表:维护内存Page和磁盘文件Page的映射关系。
      • LRU:一种数据淘汰算法;用于mysql内存数据淘汰。

3.3.2 预分配内存(内存池)

  • 最简单。先分配一大块内存空间用于装载数据。

3.3.3 数据加载单位(内存页面管理)

  • 怎么管理Page呢?通过页面映射和页面数据管理。
  • 页面映射
    • 指的是磁盘中Page和内存中Page的映射关系,就是磁盘中的某个Page该放到内存中的哪个区域。这个映射不是计算出来的,如果是计算出来的,那么磁盘中的某个Page应该放到内存中的哪块区域就固定死了,内存原本就比磁盘小,假设磁盘中有10000个Page,内存只够存放1000个Page,如果一对一映射固定死,那么磁盘中剩余9000个Page永远无法加载到内存,这岂不是操蛋了。所以页面映射的策略是内存哪里有空余,innodb就将磁盘中的Page加载到哪儿,即磁盘和内存的Page映射是动态的。
    • 页面映射关系需要维护好。磁盘中Page不仅要写到内存,内存中的Page还会回写到磁盘(例如修改记录时)。
  • 页面数据管理
    • 内存中Page如果只是读,就不需要回写到磁盘;如果修改记录,内存中Page就需要回写到磁盘。这些操作就是页面数据管理。

3.3.4 数据内外存交换(数据淘汰)

  • 内存加载满了Page,怎么加载新的Page呢?通过数据淘汰。

3.3.5 页面管理

  • 涉及术语

    • 空闲页:预加载内存中未被使用的部分。例如下图中预加载内存中的空白格子。
    • 数据页
      • Clean Page:内存中Page和磁盘中Page完全相同。例如下图Page1。
      • Dirty Page (脏页):内存中Page中有记录被修改,那么该Page需要被刷入磁盘。例如下图Page2。就是内存中发生更新的Page。
        在这里插入图片描述
  • 对于三种Page的组织管理,就是Innodb要解决的问题之一。

3.3.6 内存管理 – 内存页面淘汰

  • 一般淘汰数据使用LRU,mysql也是。
  • LRU:淘汰最久未被使用的数据,即热数据保留,冷数据淘汰。
  • 该算法的思想是一个链表头部是热数据,尾部是冷数据,删除尾部数据。实现原理是访问哪个数据,就将该数据放到表头。
  • 从磁盘加载新Page时将内存中LRU链表的尾数据删除,新数据放到链表尾。
  • 注意:如果不小心对mysql做了全表扫描,那么所有的数据都会轮询放到LRU表头,最后LRU中放的肯定是表中最后的数据,热数据就被淘汰了。如何避免这种热数据被淘汰?我可以想到两种思路:
    • 访问时间+访问频率(LFU)。LFU是Redis中的。(Redis有使用)
    • 两个URL表:将热数据放到第二张LRU表中,让全表扫描发生在第一张LRU表中。(mysql使用)

3.3.7 MySQL的LRU

  • mysql的数据淘汰策略是LRU。mysql的解决方案使用的是一个LRU链表的冷热分离,可以理解为使用的是两个LRU链表。
    在这里插入图片描述

  • 页面装载(到内存)以及页面(从内存)淘汰
    在这里插入图片描述

    磁盘中Page装载到内存的步骤:先从free list中找到一个空闲页 => 然后记录磁盘到内存该Page的映射关系 => Page链入LRU_old。
    新装载的Page肯定是先到冷数据区,接下来该Page要么被淘汰,要么被移动到热数据区。
    在这里插入图片描述

    Page链入冷数据区从head写入,Page从冷数据区淘汰从tail淘汰。
    Page从冷数据区移动到热数据区会写入到热数据区的head,Page从热数据区淘汰会从热数据区tail写入冷数据区的head。
    在这里插入图片描述

    装载Page的时会先去找空闲页,如果没有空闲页,就会先淘汰old尾部的Page => 再将磁盘的Page装载到lod尾部 => 再将尾部的Page移动到old的头。

    LRU old中尾部的Page如果正在被读,那么Page中有记录被锁着,此时尾部的Page就不能被淘汰。LRU链的是数据页,那么数据页就包含clean page和dirty page,LRU中所有的dirty page组成一个Flush list。如果LRU old尾部的Page不能淘汰,那么就会从LRU Flush list中将第一个dirty page刷盘并释放,此时这块被释放的内存就可以用来加载新的数据了。这个步骤是:先刷盘(将脏页写回磁盘) => 该页移动到LRU old尾部 => 淘汰LRU(将LRU尾部的Page链到Free list头部)=> 从Free list中找空闲页 => …。上面的步骤中,“页面移动到LRU尾部”是多余的,因为放到尾部的目的是淘汰他,所以在mysql5.2之后该步移除,直接淘汰。
    在这里插入图片描述

  • 位置移动

    • old到new
      在这里插入图片描述

      • old区冷Page移动到new热数据区的时机:innodb_old_blocks_time参数,代表Page在old区的存活时间,如果old区Page存活时间大于该参数值,就有机会进入new区。
    • new到old。热数据里最不热的是冷数据里最热的,所以从new tail移动到old head。
      在这里插入图片描述

      • new区热数据移动到old冷数据区的移动方式:并不是new tail中的Page移动到old head,而是Midpoint指针的位置左移一Page。
  • LRU_new的操作
    在这里插入图片描述

    • 链表增删改效率很高,Page有访问就移动到表头貌似很合理。但是mysql中需要考虑锁Lock。移动需要加锁,为了减少加锁次数,mysql的设计思路是减少Page移动次数。这就又涉及到两个参数:freed_page_clock(Buffer Pool 淘汰的Page数)LRU_new长度的1/4
    • 每发生一次Page淘汰,freed_page_clock的值就会+1,这是一个全局计数器。
    • 当前时刻的freed_page_clock - 上次该Page移动到LRU_new header时的freed_page_clock数 > LRU_new 长度的1/4。 这时,Page才会移动。

4. 事务实现原理

5. 索引&SQL优化

6. 锁与隔离级别、锁实现原理

6. 分库分表

7. 主键设计

8. 分片键设计

9. 实践分库分表

10. MySQL高可用部署

11. MySQL基础知识&常用SQL

12. 面试题

13. 其他

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值