Mysql一条记录是如何存储的(Innodb为主)

Mysql一条记录是如何存储的(Innodb为主)

背景:

  • MySQL 的 NULL 值会占用空间吗?
  • MySQL 怎么知道 varchar(n) 实际占用数据的大小?
  • varchar(n) 中 n 最大取值为多少?
  • 行溢出后,MySQL 是怎么处理的?

mysql文件放在哪里

查看

  • db.opt,用来存储当前数据库的默认字符集和字符校验规则。
  • .frm 存储表结构。该文件是用来保存每个表的元数据信息的,主要包含表结构定义。
  • idb 存储表数据。表数据既可以存在共享表空间文件(文件名:ibdata1)里,也可以存放在独占表空间文件(文件名:表名字.ibd)。这个行为是由参数 innodb_file_per_table 控制的,若设置了参数 innodb_file_per_table 为 1,则会将存储的数据、索引等信息单独存储在一个独占表空间,从 MySQL 5.6.6 版本开始,它的默认值就是 1 了,因此从这个版本之后, MySQL 中每一张表的数据都存放在一个独立的 .ibd 文件。后面mysql 8 版本,都合并到 ibd中

表空间文件的结构是怎么样的?

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

  1. 行:数据按行存储,并且有不同的行格式(重点)

  2. 页:数据库按页读取数据,InnoDB 的数据是按「页」为单位来读写的[按行读非常浪费IO],默认每页16Kb。页是 InnoDB 存储引擎磁盘管理的最小单元,意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。

    页的类型有很多,常见的有数据页、undo 日志页、溢出页等等。

  3. 区:为了解决B+数各层中要维持双向链表,避免相邻的页间隔大,造成大量的随机Io,就吧相邻的页划为一个区,查询的时候就可以顺序Io

    在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区(extent)为单位分配。每个区的大小为 1MB,对于 16KB 的页来说,连续的 64 个页会被划为一个区,这样就使得链表中相邻的页的物理位置也相邻,就能使用顺序 I/O 了

  4. 段:表空间是由各个段(segment)组成的,段是由多个区(extent)组成的。段一般分为数据段、索引段和回滚段等。

    • 索引段:存放 B + 树的非叶子节点的区的集合;
    • 数据段:存放 B + 树的叶子节点的区的集合;
    • 回滚段:存放的是回滚数据的区的集合,MVCC 借助回滚段实现多版本查询

Innodb行格式

以Compact 为例:

记录的额外信息:

变长字段长度列表 :

作用:保存「变长字段的真实数据占用的字节数」按16 进制逆序存放

**varchar(n) 和 char(n) 的区别是什么?**char 是定长的,varchar 是变长的,变长字段实际存储的数据的长度(大小)不固定的。

为什么「变长字段长度列表」的信息要按照逆序存放?

因为「记录头信息」中指向下一个记录的指针,指向的是下一条记录的「记录头信息」和「真实数据」之间的位置,这样的好处是向左读就是记录头信息,向右读就是真实数据,比较方便。

「变长字段长度列表」中的信息之所以要逆序存放,是因为这样可以使得位置靠前的记录的真实数据和数据对应的字段长度信息可以同时在一个 CPU Cache Line 中,这样就可以提高 CPU Cache 的命中率

同样的道理, NULL 值列表的信息也需要逆序存放。

每个数据库表的行格式都有「变长字段字节数列表」吗?

其实变长字段字节数列表不是必须的。当数据表没有变长字段的时候,比如全部都是 int 类型的字段,这时候表里的行格式就不会有「变长字段长度列表」了,因为没必要,不如去掉以节省空间。所以「变长字段长度列表」只出现在数据表有变长字段的时候

Null 值列表:按整数倍的字节值0 1 逆序存储。

当数据表的字段都定义成 NOT NULL 的时候,这时候表里的行格式就不会有 NULL 值列表了。所以在设计数据库表的时候,通常都是建议将字段设置为 NOT NULL,这样可以至少节省 1 字节的空间(NULL 值列表至少占用 1 字节空间)。

「NULL 值列表」是固定 1 字节空间吗? 是整数倍的字节数

记录头信息:

列举几个重要的:

  • delete_mask :标识此条数据是否被删除。从这里可以知道,我们执行 detele 删除记录的时候,并不会真正的删除记录,只是将这个记录的 delete_mask 标记为 1。
  • next_record:下一条记录的位置。从这里可以知道,记录与记录之间是通过链表组织的。在前面我也提到了,指向的是下一条记录的「记录头信息」和「真实数据」之间的位置,这样的好处是向左读就是记录头信息,向右读就是真实数据,比较方便。
  • record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录

记录的真实数据:

三个隐藏字段,分别为:row_id、trx_id、roll_pointer,
  • row_id 表中没有主见或者唯一约束列,Innodb会添加row_id列row_id不是必需的,占用 6 个字节。
  • 事务id,表示这个数据是由哪个事务生成的。 trx_id是必需的,占用 6 个字节。
  • roll_pointer 这条记录上一个版本的指针。roll_pointer 是必需的,占用 7 个字节。如果你熟悉 MVCC 机制,你应该就清楚 trx_id 和 roll_pointer 的作用了

varchar(n) 中 n 的最大值是多少

MySQL 规定除了 TEXT、BLOBs 这种大对象类型之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过 65535 个字节

varchar(n) 字段类型的 n 代表的是最多存储的字符数量,并不是字节大小。所以能n最大值,需要根据不同的字符集判断。

一行数据的最大字节数是 65535(不包含 TEXT、BLOBs 这种大对象类型),其中包含了 storage overhead

storage overhead= 「变长字段长度列表」和 「NULL 值列表」

「变长字段长度列表」所占用的字节数是多少?

  • 所以,我们要先知道每个变长字段的「变长字段长度」需要用多少字节表示?具体情况分为:
    • 条件一:如果变长字段允许存储的最大字节数小于等于 255 字节,就会用 1 字节表示「变长字段长度」;
    • 条件二:如果变长字段允许存储的最大字节数大于 255 字节,就会用 2 字节表示「变长字段长度」;

具体计算:MySQL 一行记录是怎么存储的? | 小林coding (xiaolincoding.com)

行溢出后,mysql如何处理

会有溢出页,并且当前位置存储溢出页的指针

m)](https://xiaolincoding.com/mysql/base/row_format.html#行溢出后-mysql-是怎么处理的)

行溢出后,mysql如何处理

会有溢出页,并且当前位置存储溢出页的指针

参考:MySQL 一行记录是怎么存储的? | 小林coding (xiaolincoding.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值