MySQL 一行记录是怎么存储的?

发布时间 2023-08-26 17:04:29作者: coooooookie

一、数据存在哪个文件

可以看到,共有三个文件,这三个文件分别代表着:

  • db.opt,用来存储当前数据库的默认字符集和字符校验规则。
  • t_order.frm ,t_order 的表结构会保存在这个文件。在 MySQL 中建立一张表都会生成一个.frm 文件,该文件是用来保存每个表的元数据信息的,主要包含表结构定义。
  • t_order.ibd,t_order 的表数据会保存在这个文件。

1、表空间文件的结构:

表空间由段(segment)、区(extent)、页(page)、行(row)组成,InnoDB存储引擎的逻辑存储结构大致如下图:

 

 数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构。

一条完整的记录分为「记录的额外信息」和「记录的真实数据」两个部分。

2、记录的额外信息包含 3 个部分:变长字段长度列表、NULL 值列表、记录头信息。

 表中的某些列可能会存储 NULL 值,如果把这些 NULL 值都放到记录的真实数据中会比较浪费空间,所以 Compact 行格式把这些值为 NULL 的列存储到 NULL值列表中。

如果存在允许 NULL 值的列,则每个列对应一个二进制位(bit),二进制位按照列的顺序逆序排列。

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

3、记录真实的数据除了我们定义的字段,还有三个隐藏字段,分别为:row_id、trx_id、roll_pointer

  • row_id:如果既没有指定主键,又没有唯一约束,那么 InnoDB 就会为记录添加 row_id 隐藏字段。row_id不是必需的,占用 6 个字节。
  • trx_id:事务id,表示这个数据是由哪个事务生成的。 trx_id是必需的,占用 6 个字节。
  • roll_pointer:这条记录上一个版本的指针。roll_pointer 是必需的,占用 7 个字节。

4、MVCC:多版本并发控制,通过版本链来控制并发事务的访问记录。

读已提交和可重复读隔离级别的事务是通过readview实现的.readview是一个数据快照,读已提交是在每个语句执行前都会生成一个readview,可重复读是启动事务时生成一个readview,然后整个事务期间都用这个readview。

4.1readview

有四个重要的字段:

  • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
  • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id。

然后数据的行格式有两个隐藏的字段:

  • trx_id:事务id,表示这个数据是由哪个事务生成的。 trx_id是必需的,占用 6 个字节。
  • roll_pointer:这条记录上一个版本的指针,对该条记录改动时会把旧版本的记录写到undo日志里,然后该指针指向旧版本记录。

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。
  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。
  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
    • 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见