InnoDB中,表包含两个部分:表结构定义、数据。
表结构:表机构定义占用的空间很小,在MySQL8.0以前是存在.fm后缀的文件中,在MySQL8.0以后,允许把表结构定义放在系统数据表中。

参数innodb_file_per_table

OFF:表的数据放在系统共享表空间,跟数据字典放在一起。
ON:每个InnoDB表数据存储在一个已.ibd为后缀的文件中。 默认值是:ON。
区别:设置为ON存储文单独的文件更容易管理,而且不需要这个表的时候,通过drop table命令,系统就会直接删除这个文件。如果设置为OFF放在共享表空间中,表即使删掉了,空间也是不会回收的。

delete删除数据

image.png
加入我们要删除一条记录R4,InnoDB引擎只会把这条记录标记为删除,磁盘文件的大小并不会缩小。之后如果要再插入一个ID在300到600之间的记录时,可能会复用这个位置,但是如果插入的是ID=800的记录,就不能复用这个位置,行记录位置的复用只限于符合范围条件的的数据。
如果删除的是整个数据页上的记录,那么整个数据页就可以被复用了,且没有符合范围条件的限制,可以复用到任何位置。
如果相邻两个数据页的利用率都很小,系统就会把这两个页上的数据合到其中一个页上,另一个数据页会被标记为可复用。
如果delete整张表,所有的数据页都会被标记为可复用,但是磁盘文件大小不变。

空洞

通过delete命令是不会回收表空间的,只能做到可复用,而实际上没有被复用的空间,看起来就像‘空洞’。
除了删除数据会造成空洞,插入数据或者更新索引上的值也会。当数据是按照索引递增插入时,索引是紧凑的。但是如果数据不按照索引递增,而是随机插入,就可能会造成索引的数据页分裂。
image.png
由于PageA满了,这是再插入一个ID=550的数据时(ID在PageA范围内),就不得不申请一个新的数据页PageB在保存记录。页分裂后,PageA的末尾就会留下空洞。
更新索引上的值,可以理解为删除一个旧值,插入一个新值,同样会造成空洞。

重建表

作用:重建表可以达到去除空洞,回收表空间的目的。
原理:新建一个与原表A结构相同的表B,然后按照主键ID递增的顺序,把数据一行一行的从A读取插入到B,这样B中的主键索引是紧凑的,数据页的利用率也高。然后用表B替换表A,从效果上看,就达到了收缩表A空间的作用。
实现:alter table A engine=InnoDB命令重建表。在MySQL5.5之前,整个DDL过程中不允许对原表有数据更新,否则会稻城数据丢失,也就是不是Online DDL。
在MySQL5.6开始,引入Online DDL,对这个流程做了优化:
1)新建一个临时文件temp,扫描表A主键的所有数据页。
2)用数据页中表A的记录,生成B+树,存储在临时文件temp中。
3)对生成temp过程中,所有对表A的操作记录保存在一个日志文件row log中。
4)temp生成完成后,将row log中的记录重放到temp中。
5)用temp替换表A。
image.png
由于row log和重放功能的存在,实现了在重建表的过程中允许对表A的增删改操作,达到了Online DDL。
实际上,DDL是需要拿到MDL写锁的,也就是alter命令在启动时需要获取MDL写锁,但是这个写锁在真正的拷贝数据之前就退化成读锁了。拿写锁的目的是保护自己的命令,禁止其他线程对这个表同时做DDL修改表结构。获取持有写锁的时间对整个DDL来说很短,之后的读锁阶段允许对标的增删改,也就认为是Online DDL了。
重建表对于大表来说,非常消耗IO和CPU,线上服务谨慎使用。

几条命令

alter table t engine=InnoDB 重建表
analyze table t 对标的索引信息重新统计,没有修改数据,过程加了MDL读锁。
optimize table t 等于 重建+analyze