13 |为什么表数据删除一半,表文件大小不变

86 阅读4分钟

InnoDB表结构存放

一个InnoDB的表包含2部分:

  1. 表结构的定义
  2. 数据

不同版本的区别:

  • MySQL8.0版本之前:表结构是存在以fm为后缀的文件里。
  • MySQL8.0版本之后:允许把表结构定义放在系统数据表中,因为表结构定义占用的空间很小。

参数 innodb_file_per_table

这个参数控制表数据存放在共享表空间里,还是存放在单独的文件中。

  • innodb_file_per_table=OFF:表数据存放在系统共享空间,也就是跟数据字典放在一起。
  • innodb_file_per_table=ON:每个innoDB表数据存储在一个以.ibd为后缀的文件中。

从MySQL 5.6版本开始,它的默认值就是ON,为什么?

  • 一个表存储为一个文件更容易管理
  • 在不需要这个表的时候,通过drop table命令,系统就会直接删除这个文件。
  • 如果是存放在共享表共建中,即使表删除了,空间也不会回收。

后续的讨论都是基于 innodb_file_per_table=ON讨论。

数据删除流程导致“空洞”

要删除R4这个记录,InnoDB引擎只会把R4这个记录标记为删除。如果之后再插入一个ID在300-600之间的记录,可能会复用这个位置,但是磁盘文件的大小并不会缩小。 image.png

删除数据页中的部分记录和删除整个数据页所有记录的区别:

  • 删除部分记录:记录复用,只限于符合范围条件的数据。
  • 删除整个数据页所有记录:整个数据页就可以被复用了,可以复用到任何位置。

数据页合并:如果相邻的2个数据页利用率都很低,系统就会把这两个页上的数据合并到其中一个页上,另外一个数据页就会被标记为可复用

如果用delelte删除整个表数据:所有的数据页都会被标记为可复用,但是磁盘上,文件不会变小,即通过delete并不能回收表空间,这些空间可被复用,看起来就像是“空洞”。

插入数据流程导致“空洞”

插入数据恰好导致页分裂,页分裂导致空洞。如下:

  • pageA满了,在插入一个ID是550的数据,需要再申请一个新的页面 pageB来保存数据,即页分裂。
  • 页分裂完成之后,pageA的末尾就留下了空洞(实际可能不止1个记录的位置是空洞)。 image.png 经过大量的增删改差的表,都可能是存在空洞的,把这些空洞去掉,就可以达到收缩表空间的目的。

重建表

重建表的思路(假设表A是一个有空洞的表):

  1. 新建一个与表A结构相同的表B
  2. 按照主键ID递增的顺序把数据行一行一行从表A里读出来再插入到表B中。(由于B是新建的表,所以表A主键索引上的空洞,在表B中就都不存在了。)
  3. 用表B替换表A,就完成了收缩表A空间的作用。

可以使用 alter table A engine = InnoDB命令来重建表。MySQL会自动完成转存数据、交换表名、删除旧表的操作。(MySQL5.5之后的功能)。

image.png 存在的问题: 往临时表查数据的过程,一般耗时最长,这个过程中,如果有新数据写入到表A,就会造成数据丢失,即这个DDL不是Online的。

MySQL5.6之后引入了Online DDL,对这个过程进行了优化,重建表流程如下:

  1. 建立一个临时文件,扫描表A主键的所有数据页。
  2. 用数据页中表A的记录生成B+树,存储到临时文件中。
  3. 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中,对应下图的state2的状态。
  4. 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态。
  5. 用临时文件替换表A的数据文件。

image.png 由于日志文件记录和重放操作这个功能的存在,这个方案在重建表的过程中,允许对表A做增删改差的操作。也是Online DDL名字的来源。

小结

收缩一个表,只是delete掉表里的数据,表文件时大小是不会改变的,要通过alter table命令重建表,才能达到表文件变小的目的。