【MySQL高级】

文章目录

  • 1. MySQL的数据目录
  • 2. 逻辑架构
  • 3. 存储引擎
  • 4. 索引的数据结构
  • 5. InnoDB数据存储结构
    • 5.1 数据库的存储结构:页
      • 5.1.1 磁盘与内存交互的基本单位 : 页
      • 5.1.2 页结构概述
      • 5.1.3 页的上层结构
    • 5.2 页的内部结构
      • 5.2.1 文件头:
      • 5.2.2 文件尾(8字节):
      • 5.2.3 Free Space(空闲空间)
      • 5.2.4 User Records(用户记录)
      • 5.2.5 Infimum+Supremum(最小最大记录)
      • 5.2.6 页目录(Page Directory):
      • 5.2.7页面头部(Page Header):
    • 5.3 记录行格式中的记录头信息
    • 5.4 从数据页的角度看B+树如何查询
    • 5.5 InnoDB行格式(或者记录格式)
      • 5.5.1 Compact行格式
    • 5.6 区, 段, 碎片区、表空间

1. MySQL的数据目录

查看:find / -name mysql
MySQL数据库文件的存放路径:/var/lib/mysql/
相关命令目录:/usr/bin(mysqladmin、mysqlbinlog、mysqldump等命令)和/usr/sbin
配置文件目录:/usr/share/mysql-8.0(命令及配置文件),/etc/mysql(如my.cnf)


表在文件系统中的表示

  1. InnoDB存储引擎模式:
    描述表结构的文件:.frm (二进制格式 存储的)
    表中数据和索引:在MySQL5.6.6以及之后的版本中,存储在独立表空间。.ibd
  2. MyISAM存储引擎模式:
    表结构:.frm
    表中数据和索引:在MyISAM中的索引全部都是 二级索引 ,该存储引擎的 数据和索引是分开存放 的。
    .frm 存储表结构
    .MYD 存储数据 (MYData)
    .MYI 存储索引 (MYIndex)

2. 逻辑架构

sql执行的流程【MySQL高级】_第1张图片

  1. 建立TCP 连接:每一个连接从线程池中获取线程
  2. SQL接口:比如SELECT … FROM就是调用SQL Interface
  3. Parser: 解析器:在解析器中对 SQL 语句进行语法分析、语义分析
  4. Optimizer: 查询优化器:生成一个执行计划 。这个执行计划表明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连接顺序(先左表还是先右表)如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户
    在查询优化器中,可以分为 逻辑查询 优化阶段和 物理查询 优化阶段。
  5. 引擎层:真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作

简化为三层结构:

  1. 连接层:客户端和服务器端建立连接,客户端发送 SQL 至服务器端;
  2. SQL 层(服务层):对 SQL 语句进行查询处理;与数据库文件的存储方式无关;
  3. 存储引擎层:与数据库文件打交道,负责数据的存储和读取。

【MySQL高级】_第2张图片

数据库缓冲池
InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,DBMS 会申请 占用内存来作为数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。
【MySQL高级】_第3张图片

缓冲池如何读取数据:
缓冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取

如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗? 不会,过一段时间再写入磁盘
【MySQL高级】_第4张图片

读都是在缓冲池中读取

我更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据持久化的保证、事务回滚都做不到还谈什么崩溃恢复?
答案:Redo Log & Undo Log

3. 存储引擎

InnoDB:事务型引擎,除了增加和查询外,还需要更新删除操作,那么,应优先选择InnoDB存储引擎

InnoDB和ACID模型:

  1. 原子方面 :ACID的原子方面主要涉及InnoDB事务,与MySQL相关的特性主要包括:
    自动提交设置。COMMIT语句。ROLLBACK语句。操作INFORMATION_SCHEMA库中的表数据。
  2. 一致性方面: ACID模型的一致性主要涉及保护数据不崩溃的内部InnoDB处理过程,与MySQL相关的特性主要包括:
    InnoDB双写缓存。
    InnoDB崩溃恢复。
  3. 隔离方面 :隔离是应用于事务的级别,与MySQL相关的特性主要包括:
    自动提交设置。SET ISOLATION LEVEL语句。
    InnoDB锁的低级别信息。
  4. . 耐久性方面: ACID模型的耐久性主要涉及与硬件配置相互影响的MySQL软件特性。由于硬件复杂多样化,耐久性方面没有具体的规则可循。与MySQL相关的特性有:
    【MySQL高级】_第5张图片

MyISAM:非事务处理存储引擎: 速度快 ,对事务完整性没有要求或者以SELECT、INSERT为主的应用

  • Archive 引擎:用于数据存档
  • Blackhole 引擎:丢弃写操作,读操作会返回空内容
  • CSV 引擎:存储数据时,以逗号分隔各个数据项
  • Federated 引擎:访问远程表
  • Memory 引擎:置于内存的表:
    • Memory同时 支持哈希(HASH)索引 和 B+树索引
    • Memory表至少比MyISAM表要 快一个数量级 。
    • 数据文件与索引文件分开存储。
    • 缺点:其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。

InnoDB与MyISAM对比

  1. InnoDB写的处理效率差一些 ,并且会占用更多的磁盘空间以保存数据和
    索引。
  2. MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据, 对内存要求较高 ,而且内存大小对性能有决定性的影响。
  3. MyISAM 不支持事务、行级锁、外键 ,有一个毫无疑问的缺陷就是 崩溃后无法安全恢复
  4. InnoDB的性能优势不只存在于长时运行查询的大型表。在同一列多次被查询时,自适应哈希索引会提高查询的速度
    【MySQL高级】_第6张图片
    【MySQL高级】_第7张图片

4. 索引的数据结构

  1. innodb中索引结构:【MySQL高级】_第8张图片
    【MySQL高级】_第9张图片

【MySQL高级】_第10张图片
聚簇索引:

  1. 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
    • 页内 的记录是按照主键的大小顺序排成一个 单向链表 。
    • 各个存放 用户记录的页 也是根据页中用户记录的主键大小顺序排成一个 双向链表 。
    • 存放 目录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个 双向链表 。
  2. B+树的 叶子节点 存储的是完整的用户记录。
    所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

优点:

  • 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  • 聚簇索引对于主键的 排序查找 和 范围查找 速度非常快

缺点:

  • 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新

二级索引(辅助索引、非聚簇索引):先由二级索引查到主键,再回表,根据主键再查出完整数据


  1. MyISAM中的索引方案:MyISAM引擎使用 B+Tree 作为索引结构,叶子节点的data域存放的是 数据记录的地址
    【MySQL高级】_第11张图片

MyISAM 与 InnoDB对比
① 在InnoDB存储引擎中,我们只需要根据主键值对 聚簇索引 进行一次查找就能找到对应的记录,而在MyISAM 中却需要进行一次 回表 操作,意味着MyISAM中建立的索引相当于全部都是 二级索引 。
② InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是 分离的 ,索引文件仅保存数据记录的地址。
③ InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
④ MyISAM的回表操作是十分 快速 的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
⑤ InnoDB要求表 必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

索引的代价

  • 空间上的代价
    每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
  • 时间上的代价
    每次对表中的数据进行 增、删、改 操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每层节点都是按照索引列的值 从小到大的顺序排序 而组成了双向链表。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些 记录移位页面分裂页面回收 等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。

Hash结构效率高,那为什么索引结构要设计成树型呢?
【MySQL高级】_第12张图片
采用自适应 Hash 索引目的是方便根据 SQL 的查询条件加速定位到叶子节点,特别是当 B+ 树比较深的时候,通过自适应 Hash 索引可以明显提高数据的检索效率。

B树【MySQL高级】_第13张图片
B+ 树和 B 树的差异:

  1. B+有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数+1。
  2. B+树,非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
  3. B+非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录 。
  4. B+所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。

5. InnoDB数据存储结构

5.1 数据库的存储结构:页

5.1.1 磁盘与内存交互的基本单位 : 页

磁盘与内存交互的基本单位 : ,InnoDB将数据划分为若干个页, InnoDB中页的大小默认为16KB

以页作为磁盘和内存之间交互的基本单位, 也就是一次最少从磁盘中读取16KB的内容到内存中, 一次最少把内存中的16KB内容刷新到磁盘中. 也就是说, 在数据库中, 不论读一行还是读多行, 都是将这些行所在的页进行加载. 也就是说, 数据库管理存储空间的基本单位是页(Page), 数据库I/O操作的最小单位是页. 一个页中可以存储多行记录
【MySQL高级】_第14张图片

5.1.2 页结构概述

页a, 页b, 页c… 页n这些页可以不在物理结构上相连, 只要通过双向链表相关联即可. 每个数据页中的记录会按照主键值小到大的顺序组成一个单向链表, 每个数据页都会为存储在它里面的记录生成一个页目录, 在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽位, 然后再遍历该槽对应分组中的记录即可快速找到指定的记录

  • 注意: 这些页是同一层的页, 可以不在物理结构上相连, 但是页分配的时候确实是相连的,
  • 注意: 数据库分配的单位是, 段又分为了索引段数据段, 数据段就是索引结构的叶子结点层, 所以说其实叶子结点层的页是相连的

5.1.3 页的上层结构

另外在数据库中, 还存在着区(Extent), 段(Segment)和表空间(Tablespace)的概念. 行, 页, 区, 段, 表空间的关系如下图所示:【MySQL高级】_第15张图片

  • 区(Extent)是比页大一级的存储结构, 在InnoDB存储引擎中, 一个区会分配64个连续的页. 因为InnoDB中的页的大小默认是16KB, 所以一个区的大小是64*16KB = 1MB.

  • 段(Segment) 由一个或者多个区组成, 区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页), 不过在段中不要求区域区是相邻的. 段是数据库中的分配单位, 不同类型的数据库对象以不同的段形式存在, 当我们创建数据表, 索引的时候, 就会相应创建对应的段, 比如创建一张表时会创建一个表段, 创建一个索引时会创建一个索引段

  • 表空间(Tablespace) 是一个逻辑容器, 表空间存储的对象是段, 在一个表空间中可以有一个或者多个段, 但是一个段只能属于一个表空间. 数据库由一个或者多个表空间组成, 表空间从管理上可以划分为系统表空间, 用户表空间, 撤销表空间, 临时表空间

5.2 页的内部结构

页如果按照类型划分的话, 常见的有数据页(保存B+树结点), 系统页, Undo页事物数据页等, 数据页是我们最常使用的页

数据页的16KB大小的存储空间被划分为了7个部分, 分别是文件头(File Header), 页头(Page Header), 最大最小记录(Infimum+supremum), 用户记录(User Records), 空闲空间(Free Space), 页目录(Page Directory)和文件尾(File Tailer)
【MySQL高级】_第16张图片
【MySQL高级】_第17张图片

5.2.1 文件头:

作用 : 描述各种页的通用信息(比如页的编号, 其上一页, 下一页是谁等等)
【MySQL高级】_第18张图片

  1. fil_page_offset(4个字节) :
    每一个页都有一个单独的页号, 就和你的身份证号码一样, InnoDB通过页号可以唯一定位一个页
  2. fil_page_type
    这个代表当前页的类型
    【MySQL高级】_第19张图片
  • 可以看到页其实是不区分数据页和索引页的, 我们说索引页也好, 说数据页也好都是代表的是索引 或者 数据

  • 段是分为索引段数据段的, 因为我们要区分索引和数据进行分开存储, 至于好处就是为了能多一点顺序IO, 减少随机IO

  1. fil_page_prev(4字节)和fil_page_next(4字节)
    InnoDB都是以页为单位来存放数据的, 如果数据分散到多个不连续的页中存储的话需要把这些页关联起来, fil_page_prev和fil_page_next就分别代表本页的上一个和下一个页的页号. 这样通过建立一个双向链表把许许多多的页就串联起来了

  2. fil_page_space_or_chksum(4字节)
    代表当前页面的校验和(checksum)

  • 文件头部和文件尾部都有属性fil_page_space_or_chksum

校验和的作用:
每当一个页面在内存中修改了, 在同步之前就要把它的校验和算出来. 因为File Headeer在页面的前面, 所以校验和会被首先同步到磁盘, 当完全写完时, 校验和也会被写到页的尾部, 如果完全同步成功, 则页的首部和尾部的校验和应该是一致的. 如果写一般断电了, 那么在File Header中的校验和就代表着已经修改过的页, 而在File Trailer中的校验和代表着原先的页, 二者不同则意味着同步中间出了错. 这里, 校验方式就是采用Hash算法进行校验

  1. fil_page_lsn(8字节)
    页面被最后修改时对应的日志序列位置(英文名是: Log Sequence Number)
    日志序列位置也是为了校验页的完整性的, 如果首部和尾部LSN值校验不成功的话, 就说明同步过程出现了问题

5.2.2 文件尾(8字节):

  1. 前4个字节代表页的校验和
    这个部分是和文件头(File Header)中的校验和相对应的

  2. 后4个字节代表页面被最后修改时对应的日志序列位置(LSN):
    这个部分也是为了校验页的完整性的, 如果首部和尾部的LSN值校验不成功的话, 就说明同步过程出现了问题

5.2.3 Free Space(空闲空间)

我们自己存储的记录会按照指定的行格式存储到User Records(用户记录)部分, 但是在一开始生成页的时候, 其实并没有User Records这个部分, 每当我们插入一条记录, 都会从Free Space部分, 也就是尚未使用的存储空间申请一个记录大小的空间划分到User Records部分, 当Free Space部分的空间全部被User Records部分代替掉之后, 也就意味着这个页使用完了, 如果还有新的记录插入的话, 就需要去申请新的页了

5.2.4 User Records(用户记录)

User Records中的这些记录按照指定的行格式一条一条摆在User Records部分, 相互之间形成单链表

5.2.5 Infimum+Supremum(最小最大记录)

InnoDB规定了最小记录与最大记录这两条记录的构造十分简单, 都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的, 如图所示:【MySQL高级】_第20张图片
这两条记录不是我们自己定义的记录, 所以它们并不存放在页的User Records部分, 它们被单独放在了一个称为Infimum+Supremum的部分, 如图所示:【MySQL高级】_第21张图片
有了 Infimum Record 和 Supremum Record ,现在查询不需要将某一页的 User Records 全部遍历完,只需要将这两个记录和待查询的目标记录进行比较。比如我要查询的数据 id = 101 ,那很明显不在当前页。接下来就可以通过下一页指针跳到下页进行检索。

可能有人又会说了,你这 User Records 里不也全是单链表吗?即使我知道我要找的数据在当前页,那最坏的情况下,不还是得挨个挨个的遍历100次才能找到我要找的数据?你管这也叫效率高?

不得不说,这的确是个问题,不过是一个 MySQL 已经考虑到的问题。不错,挨个遍历确实效率很低。为了解决这个问题,MySQL 又在页中加入了另一个区域 Page Directory

顾名思义,Page Directory 是个目录,里面有很多个槽位(Slots),每一个槽位都指向了一条 User Records 中的记录。大家可以看到,每隔几条数据,就会创建一个槽位

MySQL 会在新增数据的时候就将对应的 Slot 创建好,有了Page Directory,就可以对一张页的数据进行粗略的二分查找。至于为什么是粗略,毕竟 Page Directory 中不是完整的数据,二分查找出来的结果只能是个大概的位置,找到了这个大概的位置之后,还需要回到 User Records 中继续的进行挨个遍历匹配。

5.2.6 页目录(Page Directory):

在页中, 记录是以单向链表的形式进行存储的, 单向链表的特点就是插入, 删除非常方便, 但是检索效率不高, 最差的情况下需要遍历链表上的所有结点才能完成检索. 因此在页结构中专门设计了页目录, 专门给记录做一个目录, 通过二分查找法的方式进行检索, 提升效率
【MySQL高级】_第22张图片
由于槽指向的是一组中最大的值, 所以如果我们判断到某个值比我们的上一个槽值大, 比下一个槽值小的时候, 那么我们就应该到上一个槽的位置, 上一个槽指向的就是上一个页目录中最大的, next_record指针指向的就是下一个槽中的最小值了, 因为页内是单向指针, 所以我们必须要从前往后找

5.2.7页面头部(Page Header):

为了能得到一个数据页中存储的状态信息, 比如本页中已经存储了多少条记录, 第一条记录的地址值是什么, 页目录中存储了多少个槽等等, 特意在页中定义了一个叫做Page Header的部分, 这部分占用固定的56个字节, 专门存储各种状态信息

5.3 记录行格式中的记录头信息

【MySQL高级】_第23张图片
【MySQL高级】_第24张图片

  • 由于表中是指明了主键的, 所以隐藏字段只有两个 : 1.transaction_id(事物id)和2. roll_pointer(回滚指针), 如果没有指明主键, 也没有指明非空且唯一的字段, 那么就会有一个row_id隐藏字段作为聚簇索引列
  1. delete_mask
    这个属性标记着当前记录是否被删除, 占用1个二进制位
    值为0 : 代表记录并没有被删除
    值为1 : 代表记录被删除掉了
  • 这些被删除的记录之所以不立即从磁盘上移除, 是因为移除它们之后其他的记录在磁盘上需要重新排列, 导致性能消耗. 所以只是打一个删除标记而已, 所有被删除掉的记录都会组成一个所谓的垃圾链表, 之后如果有新记录插入到表中的话, 可能把这些被删除的记录占用的存储空间覆盖
  1. min_rec_mask
    B+树的每层非叶子结点最小记录都会添加该标记, min_rec_mask值为1

  2. record_type
    这个属性表示当前记录的类型, 一共有4种类型的记录:
    0 : 表示普通记录
    1 : 表示B+树非叶子结点记录
    2 : 表示最小记录
    3 : 表示最大记录

  3. heap_no
    这个属性表示当前记录在本页中的位置
    我们插入的4条记录在本页中的位置分别是: 2,3,4,5最小记录和最大记录的heap_no值分别是0和1

  4. n_owned
    页目录中每个组最后一条记录的头信息中会存储该组一共多少条记录, 作为n_owned字段
    可以看到n_owned和页目录是密切相关的

  5. next_record
    记录头信息里该属性非常重要, 它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量

删除操作举例:【MySQL高级】_第25张图片
【MySQL高级】_第26张图片

添加操作:
【MySQL高级】_第27张图片
【MySQL高级】_第28张图片
说明:

当数据页中存在多条被删除掉的记录时, 这些记录了的next_record属性将会把这些被删除掉的记录组成一个垃圾链表, 以备之后重用这部分存储空间

5.4 从数据页的角度看B+树如何查询

  1. B+树是如何进行记录检索的:
    如果通过B+树的索引查询行记录, 首先是从B+树的开始, 逐层检索, 直到找到叶子结点, 也就是找到对应的数据页位置, 将数据页加载到内存中, 页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组, 然后再在分组中通过链表遍历的方式查找记录

  2. 普通索引和唯一索引在查询效率上有什么不同?
    我们创建索引的时候可以是普通索引, 也可以是唯一索引, 那么这两个索引在查询效率上有什么不同?
    唯一索引就是普通索引上增加了约束性, 也就是关键字唯一, 找到了关键字就停止检索, 因为关键字不重复. 而普通索引, 可能会存在用户记录中的关键字相同的情况, 根据页结构的原理, 当我们读取一条记录的时候, 不是单独将这条记录从磁盘中读取出去, 而是将这个记录所在页加载到内存中进行读取, InnoDB存储引擎的页大小为16KB, 在一个页中可能存储着上千条记录, 因此在普通索引的字段上进行查找也就是在内存中多几次判断(判断下一条记录是不是值也满足)操作, 对于cpu来说, 这些操作锁耗费的时间是可以忽略不计的. 所以对一个索引字段进行检索, 采用普通索引还是唯一索引在检索效率上基本没有差别.

5.5 InnoDB行格式(或者记录格式)

我们平时的数据以行为单位来向表中插入数据, 这些记录在磁盘上的存储方式也被称之为是"行格式"或者"记录格式". InnoDB存储引擎设计了4中不同类型的"行格式", 分别是’Compact’, ‘Redundant’, ‘Dynamic’, 'Compressed’行格式

5.5.1 Compact行格式

一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两大部分:
【MySQL高级】_第29张图片
但是其实记录的真实数据部分还有隐藏字段: row_id(可能有), tri_id, roll_point

变长字段长度列表:
比如varchar(M), varbinary(M), text类型, blob类型, 变长字段中存储多少字节的数据不是固定的, 在Compact行格式中, 把所有变长字段的真实数据占用的字节长度都存放在了记录的开头不为, 从而形成了一个变长字段长度列表.

NULL值列表:

Compact行格式会把可以为NULL的列统一管理起来, 存在一个标记为NULL值的列表中.
如果表中没有允许存储为NULL的列, 则NULL列值列表也不存在了(所有列都是非空列的时候

  • 二进制位的值为1时, 表示该列的值为NULL

  • 二进制位的值为0时,代表该列的值不为NULL

记录的真实数据:
记录的真实数据除了我们自己定义的列的数据以外, 还会有三个隐藏列:【MySQL高级】_第30张图片

  • 一个表没有手动定义主键, 则会选择一个not NULL and Unique键作为主键, 如果也没有非空且唯一键, 则会为表默认添加一个名为row_id的隐藏列作为主键. 所以row_id是在没有自定义主键以及非空且唯一的情况下才会存在的
  • 事物ID回滚指针在后面事物日志笔记中会详细讲解

找主键也罢, 找唯一非空也罢, 生成隐藏列(非空且唯一的row_id)也罢, 其实都是为了生成聚簇索引, 有聚簇索引才能存储数据

行溢出:一个页的大小一般是16KB, 也就是16384字节, 而一个varchar(M)类型的列就可以存储65533个字节的真实数据, 当然如果加上变长字段列表, 最终就是65535个字节, 这样就可能出现一个页存放不了一条记录, 这种现象称之为 : 行溢出

Dynamic和Compressed行格式:
MySQL5.7和8.0中默认行格式都是Dynamic, Dynamic, Compressed行格式和Compact行格式都很像, 只不过在处理行移除数据时有分歧:

  • Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式. 在数据页中只存放20个字节的指针(溢出页的地址和长度), 实际的数据都存放在了Off page(移除页)中

5.6 区, 段, 碎片区、表空间

为什么要有区?:让一部分的页连续, 也就是让一个区中的页都连续存储
B+树的每一层都会形成一个双向链表, 如果是以页为单位来分配存储空间的话, 双向链表相邻的两个页之间的物理位置可能会离得非常远. 我们介绍B+树索引的使用场景的时候特别提到范围查询只需要定位到最左边的记录最右边的记录, 然后沿着双向链表一直扫描就可以了, 而如果链表中相邻的两个页物理位置离得非常远, 这就时所谓的随机I/O. 再一次强调, 磁盘的速度和内存速度差了好几个数量级, 随机I/O是非常慢的, 所以我们应该尽量让链表中相邻的页的物理位置也相邻, 这样进行范围查询的时候才可以使用所谓的顺序I/O

一个区就是在物理位置上连续的64个页. 1MB. 在表中数据量大的时候, 为某个索引分配空间的时候就不再按照页为单位分配了, 而是按照区为单位分配, 甚至在表中的数据特别多的时候, 可以一次性分配多个连续的区, 虽然可能造成一点点空间的浪费(数据不足以填充满整个区), 但是从性能角度看, 可以消除很多随机I/O, 功大于过!

为什么要有段?
对于范围查询, 而如果不区分叶子节点和非叶子节点, 统统把节点代表的页面放到申请到一个区中的话, 进行范围扫描的效果就大打折扣了
所以InnoDB对B+树的叶子结点和非叶子结点进行了区别对待, 也就是说叶子结点有自己的独有的区, 非叶子结点也有自己独有的区.

常见的段有数据段, 索引段, 回滚段. 数据段即为B+树的叶子结点, 索引段即为B+树的非叶子结点

为什么要有碎片区?

碎片区中的页可以用于不同的目的, 比如有些页用于段A, 有些页用于段B, 有些页甚至哪个段都不属于. 碎片区直属于表空间, 并不属于任何一个段

所以以后为某个段分配存储空间的策略是这样的:
在刚开始向表中插入数据的时候, 段是从某个碎片区单个页面为单位来分配存储空间的
当某个段已经占用了32个碎片区页面之后, 就会申请以完整的区为单位来分配存储空间.
也就是某个段已经是在32个碎片区中分配了页面了, 这个时候就会以完整的区为单位来分配存储空间

所以现在段不能仅仅定义为是某些区的集合, 更精确的说应该是某些零散的页面以及一些完整的区的集合

表空间和段都是逻辑概念, 区和页和行是物理结构, 段是以区和零散的页面为分配单位的

表空间可以看做是InnoDB存储引擎逻辑结构的最高层, 所有的数据都存放在表空间中.

表空间从管理上划分为系统表空间(System tablespace), 独立表空间(File-per-table tablespace), 撤销表空间(Undo Tablespace)和临时表空间(Temporary Tablespace)等

你可能感兴趣的:(数据库,数据库)