首页 > 技术文章 > mysql数据存储格式---避免行溢出

tommaoxiaoqi 2020-04-20 08:33 原文

InnoDB是将数据存在磁盘上的存储引擎。

查询需要将磁盘数据读到内存来处理,修改删除需要将内存数据写入磁盘。磁盘IO是非常慢的,所以,以页(16KB)作为内存和磁盘交互的基本单位。

 

行格式 (row_format)

insert一条数据,该数据就包含在页中,每条数据存储有一定的格式要求,通过ROW_FORMAT来指定。如:CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称;

row_format有四种格式:CompactRedundantDynamicCompressed

 

 规定:每行最多存65535字节(64KB,BLOB,TEXT除外)。超过大小,创表失败。

 

 utf8 一个字符包括1~3个字节。

 

行溢出 

一页最大16KB而一条记录最大可64KB,存在一页存不下一行记录的情况即行溢出

 一个页要求最少存2条记录,加上其他额外信息,大概一条记录超过7KB左右即可能存在行溢出。

对于出现行溢出的情况,Compact行格式是在真实数据中存一部分该行的数据(768个字节)+其他页的地址,把剩下数据放到其他页中。

页的信息存在磁盘上,内存中没有的化,又会涉及磁盘IO。设计表结构的时候尽量不要使用大字段,包括TEXT,BLOB都会存在行溢出。

 

Redundant行格式类似,只是额外信息不同记录的不同。

而Dynamic和Compressed在处理行溢出时不存真实数据,全放到其他页中,只存其他页的地址。

Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。

以下未特殊说明都是以Compact为例:

 

每条记录间的关系:

记录在页中是按主键递增的顺序排列,在头信息中有指向下条记录的地址(单链表)。

 

删除记录并没有真实删除:

记录头信息中有标志位,记录该条记录是否删除。因为真的从磁盘中删除,如删除多条记录的话又不是按主键顺序删的,可能会造成其他记录在磁盘上重新排列。而且在MVCC多版本并发控制的时候也可以记录之前的版本信息。

所以用标志位标记,单链表指针将其跳过。删除的记录形成可重用空间,新增的时候覆盖原先的数据。

 

页结构:

页内存放多条用户记录,页内的目录索引,页间的双向链表指针等。

记录在页内的存放形式:

用户的每条数据放到User Recouds里,用单链表连接。同时对这些记录进行分组,最多8条一组。把每组的最大主键的地址放到Page Directory中记录,方便查找。

因为记录是按主键排序排列而且有页内的目录对主键进行索引,所以按主键查找非常快。

过程:一个主键到了某个页之后,首先根据页内的目录索引按照二分法确定该主键所在的组。然后通过单向链表遍历该组所有的记录。

 

 

 

页与页直接的关系:双链表连接。

每个数据页的File Header部分都有上一个和下一个页的编号,所以所有的数据页会组成一个双链表

使用双向链表 因为在插入一条记录时,当前页已满可以看临近的页有没有空余空间,这样只需在两个页间移动记录,不用分裂当前页。

 

主键递增

因为所有记录默认按主键递增排列,所以插入数据时尽量保证主键是递增的,避免数据的移动。

用DB本身的AutoIncrement字段的话只能保证单个数据库递增,但分库的分布式场景下,没法保证整体递增和全局唯一性。用代码控制摆脱某一个数据库,采用雪花算法生成主键ID(根据当前时间等参数运算生成随机数保证递增),当然redis缓存也可生成递增的数值。

推荐阅读