Mysql探索(二)之InnoDB底层存储结构

了解mysql底层存储结构,知道db数据是怎么存储的,这对于深入了解mysql底层运行原理尤为关键,一看到crud操作时,脑海若能浮现底层数据运作情况,则为绝佳。如果懂得索引,buffer,log等InnoDB底层技术,再看这篇,相信也会有意想不到的收获。

文章目录

      • InnoDB存储形式
        • 表空间(tablespace)
        • 段(segment)
        • 区(extent)
        • 基本单位-页(page)
        • 最小存储数据单位 - 行记录

InnoDB存储形式

当我们update/insert数据到数据库时,并不是直接操作磁盘内存的,而是将数据先更新到内存中,最后才刷到磁盘中持久化,同理,当我们read数据时,如果数据不在内存中,则需要将磁盘中的数据按页为单位来加载进内存中。内存读取效率高,但是会有数据丢失的风险,这些都是InnoDB需要考虑周全的。
Mysql探索(二)之InnoDB底层存储结构_第1张图片
Innodb体系结构是由内存结构,线程,磁盘文件三部分组成的。
Innodb逻辑存储单元由大到小分为:表空间(tablespace) -> 段(segment) -> 区(extent) -> 页(page)
Mysql探索(二)之InnoDB底层存储结构_第2张图片

表空间(tablespace)

对于Innodb来说,所有的数据都是存储在表空间中的,当我们安装数据库初始化数据时,系统会创建一个系统表空间,文件名为ibdata1,它会存储系统数据相关信息和回滚段的信息,双写缓冲区也在系统表空间里。
在data目录下面可以找到ibdata1文件。
ibdata1

ibdata1默认的初始化大小空间为12M,自动扩展是64M,建议将空间大小扩大为1GB。
Mysql探索(二)之InnoDB底层存储结构_第3张图片
除了系统表空间,还有独立表空间,当我们创建表的时候,会给表创建一个独立表空间,表的相关数据信息会存储在自己的表空间中,比如B+树数据,索引,插入缓冲等信息。独立表空间会有两个文件,一个是.frm文件,一个是.ibd,.frm文件是存储表结构的,.ibd文件是存储表数据的。
ibd

段(segment)

表空间里划分了段的单元逻辑,单纯是一种逻辑概念,也就是说不是物理连续的内存,mysql为了方便查找叶子节点的数据,就将B+树的叶子节点存储在一个段里,非叶子节点就又统一存储在另外一个段里。也就是说表里有一个索引就需要两个段来存储该索引数据。
而每个段呢则由N个区和32个零散的页组成的。

区(extent)

区是由连续的页组成的,这里的连续是指物理上的连续,每个区由64个页组成,而每个页的默认大小是16kb,所以区的默认大小是64*16kb=1MB。Innodb都是按页来读取数据的,所以页需要连续的,这样才能避免随机IO。

基本单位-页(page)

页是Innodb操作内存结构的最小单位了,所有数据都是按页从磁盘中读取到内存中的,索引也是按页来作为节点的。页的默认大小是16kb,可以查看innodb_page_size属性:
Mysql探索(二)之InnoDB底层存储结构_第4张图片
页里面存储着很多行记录,一个页最少存储着两行数据,虚拟最小行(Infimum records)和虚拟最大行(Supremum records),用来限定行记录的范围。

那我们来看看页结构,主要有7块信息:
Mysql探索(二)之InnoDB底层存储结构_第5张图片

从File Header信息就可以看出来页节点是双向链表的,其他信息看上图。

File Trailer这个需要说明下:
科班的应该知道操作系统的基本单位也是页,但是页的大小是4kb,当Innodb把数据从磁盘中加载到内存中时,是需要加载4倍的操作系统页数,同样的,把数据从内存中刷到磁盘时也是一样的道理。那就会出现这样的一种情况:从操作系统中加载数据到内存中时,有可能加载了3*4kb的时候出现状况了,导致内存中页的数据不全,缺失了一个操作系统的页数据,这时候就需要File Trailer来校验数据的完整性了。它是通过比较校验和来验证的,File Header里存储着该记录的校验和,File Trailer存储着同样的校验和,匹配不上的时候就是记录不完整。

页数据大小是16kb,我们来算个数,假设有一张表,10个integer型和10个char(20)型的字段,则一个记录大概占104+1020=240个字节,则一个页可以16*1024/240 ~= 68条记录,当Innodb按索引定位到了该页记录时,是不是还得一个个遍历过去查找需要的记录数据,这样效率就会非常的低下了,你看hashmap的链表超过了8个都要变成红黑树了,想象下就知道68个数据遍历完是会影响效率的。

Page Directory
所以记录数的链表也需要被改善,Innodb用了一种类似跳表的结构,就是对所有记录数进行按固定数(一般4~8个)划分,每划分一块叫做槽,然后会将每个槽中最后一条记录的地址存储起来,放置在page directory里,所以page directory类似于目录树一样的东西。
Mysql探索(二)之InnoDB底层存储结构_第6张图片
通过槽来定位就会减少检索的记录数,提高效率。

最小存储数据单位 - 行记录

我们存储数据时,都是论行来将数据存进db的,而InnoDB将数据存储进磁盘时,也是按行记录存储数据的。

Innodb有两种格式文件:Antelope和Barracuda。
Antelope文件格式下有两种行记录:Compact,Redundant
Barracuda文件格式下有两种行记录:Dynamic,Compressed。
Mysql探索(二)之InnoDB底层存储结构_第7张图片
5.7版本之后默认使用的是dynamic格式,如上图命令可查看。

  • Redundant:最早的格式,会占用很多空间,基本不被考虑。
  • Compact:以前版本用的最多的格式,该格式下,记录所在的当前页只会存储每个列的前768个字节,也就是说如果列的占用空间超过768个字节,则超出的部分数据存储在其他页中,还有20个字节存储列的数据长度+指向外部存储页的指针,也就是列占用当前记录的768+20空间字节。
  • Dynamic:5.7版本后默认的记录格式,可以说是一种变种的Compact格式,所有的数据都存储在溢出页中,当前页只存储了指向溢出页的指针,占用20字节。
  • Compressed:压缩格式,会将数据进行压缩存放到磁盘中,大概一半的压缩率,但是读取到内存中时还需要解压缩,浪费cpu资源。

行溢出:就是当前页记录存储不下列的内容,会将多余的数据存储在其他页(溢出页)中。主要针对一些大数据blob、text等数据。varchar类型也是,一个varchar类型最多存储2的32次方,即65535个字节,一个页放不下。

行记录的通用格式:
Mysql探索(二)之InnoDB底层存储结构_第8张图片

可以看到行记录存储了很多信息:

  • 哪些字段是varchar类型的,占用多少空间;
  • 哪些字段是null的
  • 表无唯一主键是会默认创建一个隐藏主键row_id
  • 记录的事务id
  • 记录的回滚指针
  • 头信息
    • 该记录是否被删除了
    • 是否是页中的最小记录
    • 该记录所在槽位有多少记录数
    • 记录的类型
    • 下一个记录的指针

相信看官们了解记录结构之后也会产生和我当时一样的疑惑:
q:记录结构这么紧凑,记录|记录|记录|…, 而表中又有varchar可变长类型,那我update数据的时候mysql该怎么办呢?

让mysql把后面的记录数据往后挪动磁盘内存地址肯定不现实,所以mysql根据情况采取了两种方式:

  • update中的字段不包含主键:
    • 如果update的字段数据值长度一样,则直接在原地址进行update,即就地更新。
    • 如果不等长,则先删除掉原来的记录数据,再插入一条新数据。此处采用的是物理删除
  • update中的字段包含主键:
    • 先给原记录的delete mark标识记为1,然后再插入一条新的记录。此处采用的是逻辑删除

来个完整的结构图:
Mysql探索(二)之InnoDB底层存储结构_第9张图片

到这里应该就能对Innodb引擎的存储结构有个清晰认识了。

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