InnoDB表结构存放
一个InnoDB的表包含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之间的记录,可能会复用这个位置,但是磁盘文件的大小并不会缩小。
删除数据页中的部分记录和删除整个数据页所有记录的区别:
- 删除部分记录:记录复用,只限于符合范围条件的数据。
- 删除整个数据页所有记录:整个数据页就可以被复用了,可以复用到任何位置。
数据页合并:如果相邻的2个数据页利用率都很低,系统就会把这两个页上的数据合并到其中一个页上,另外一个数据页就会被标记为可复用。
如果用delelte删除整个表数据:所有的数据页都会被标记为可复用,但是磁盘上,文件不会变小,即通过delete并不能回收表空间,这些空间可被复用,看起来就像是“空洞”。
插入数据流程导致“空洞”
插入数据恰好导致页分裂,页分裂导致空洞。如下:
- pageA满了,在插入一个ID是550的数据,需要再申请一个新的页面 pageB来保存数据,即页分裂。
- 页分裂完成之后,pageA的末尾就留下了空洞(实际可能不止1个记录的位置是空洞)。
经过大量的增删改差的表,都可能是存在空洞的,把这些空洞去掉,就可以达到收缩表空间的目的。
重建表
重建表的思路(假设表A是一个有空洞的表):
- 新建一个与表A结构相同的表B
- 按照主键ID递增的顺序把数据行一行一行从表A里读出来再插入到表B中。(由于B是新建的表,所以表A主键索引上的空洞,在表B中就都不存在了。)
- 用表B替换表A,就完成了收缩表A空间的作用。
可以使用 alter table A engine = InnoDB命令来重建表。MySQL会自动完成转存数据、交换表名、删除旧表的操作。(MySQL5.5之后的功能)。
存在的问题: 往临时表查数据的过程,一般耗时最长,这个过程中,如果有新数据写入到表A,就会造成数据丢失,即这个DDL不是Online的。
MySQL5.6之后引入了Online DDL,对这个过程进行了优化,重建表流程如下:
- 建立一个临时文件,扫描表A主键的所有数据页。
- 用数据页中表A的记录生成B+树,存储到临时文件中。
- 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中,对应下图的state2的状态。
- 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态。
- 用临时文件替换表A的数据文件。
由于日志文件记录和重放操作这个功能的存在,这个方案在重建表的过程中,允许对表A做增删改差的操作。也是Online DDL名字的来源。
小结
收缩一个表,只是delete掉表里的数据,表文件时大小是不会改变的,要通过alter table命令重建表,才能达到表文件变小的目的。