总览
首先总的来说,分为四个层级,行页区段。行就是数据库里的一行数据。
但一次从磁盘读进内存的数据量是一页(页是读写的单位,默认16KB一页),页分很多种类,例如数据页、溢出页、undo日志页。
而我们知道B+树的叶子节点是逻辑上连续的,假设物理上不连续,那随机io的开销还是很大的,所以把不同页整合到一起还是有必要的,这就是区,假设每个区1MB,那对16KB的页,最多连续64个页能存进连续的一段空间,加速了范围访问。
段分为数据段、索引段、回滚段。数据段就是叶子节点的所有区,索引段就是非叶子节点的所有区,回滚段存放的是回滚数据。
行数据
变长字段长度列表(若没有varchar,则该字段是不存在于行中的)
存储varchar类型的当前数据长度,例如下图
假设字符集时ascii(一个字符一个字节),则第一条记录,name占用一个字节,即0x01,phone占用三个字节,即0x03,在变长字段长度列表中,要按照倒序(千万要注意!不是按照字节大小,是按照列的顺序的倒叙即列n、列n-1…列1),如下图
同理,第二条第三条数据(NULL值不会被存到“记录的真实数据”,所以也不用在变长字段长度列表里存储)
那么为啥要倒着存储字段的长度呢?
答案是,为了尽可能一次读取数据时,让真实数据和长度被读进一个cache line里,提高命中率
NULL值列表(设定了NOT NULL则也不存在于行中)
分配整数个字节,然后一个字节可以表示8个列的是否为NULL的情况(不足8个也要分配一个字节,超过则两个字节),同样
对于第一条数据,没有NULL值,则全是0
第二条数据age是NULL
第三条数据age和phone是NULL
总体来看,目前状况是这样的:
记录头信息
- delete_mask,是1代表本行被删除,也就是说删除数据不是真删了,只是赋值了标志位罢了
- next_record,指向了下一条记录(指向位置是记录的额外信息和记录的真实数据中间,往左往右就可以访问二者,高效)
- record_type,表示当前行的类型,0代表普通记录、1代表非叶子节点、2代表最小记录、3代表最大记录
记录的真实数据
这是三个隐藏列,是MySQL自动为每个记录添加的
- row_id,假设建立表的时候没有主键或者唯一约束列,就会有这个隐藏列,否则就没有。占6字节
- trx_id,事务id,代表数据由哪些事务产生,是必须的。占6字节
- roll_pointer,记录上一个版本的指针,是必须的。占7字节
varchar(n)最大取值?
MySQL约束的一行记录最多65535(TEXT、BLOB这种大对象类型除外),这既包括额外信息,还包括真实数据,但不包括记录头信息和隐藏列。那来算一下把。
- 假设只有一个列,并且这个列是varchar。如果设定可以为NULL则要-1,否则不考虑NULL值列表。变长字段长度列表分两种情况:1、最大长度低于256( 2 8 2^8 28),则分配一字节记录2、大于256,则分配两字节( 2 1 6 = 65536 2^16=65536 216=65536)记录。很显然本例子是第二种情况,因此65535-1-2=65532字节。那么对于ascii的字符集,n=65532,utf8则是n=65532/3
- 假设两列,和上述一样,看下图
行溢出的处理
通常默认一个页16KB,16*1024=16384个字节,少于65532,造成了可能一个页都放不下一行
对于Compact行格式,会把溢出部分存到溢出页:
对于compressed、dynamic行格式,记录真实数据的地方只会存放指针了,数据全部存到溢出页