0%

重建表回收空间

一个InnoDB表包含两部分:表结构定义和数据。
MySQL8.0以前,表结构是存在以.frm为后缀的table_name.frm文件里。MySQL8.0版本,允许把表结构定义放在系统数据表中。表结构定义占用的空间很小。

表数据存储位置

表数据既可以存在共享表空间中,也可以是单独的文件。

  1. innodb_file_per_table=OFF,表示表的数据放在系统共享表空间,也就是跟数据字典放在一起。
  2. innodb_file_per_table=ON,(建议)表示每个InnoDB表数据存储在一个以.ibd为后缀的table_name.ibd文件中

MySQL5.6.6版本开始,该参数默认值为ON。
一个表单独存储为一个文件更容易管理,而且在不需要这个表的时候,通过drop table命令,系统就会直接删除这个文件。而如果是放在共享表空间,即使表删掉了,空间也是不会回收的。

针对表中某些行被删除,表空间没有被回收。

数据页空洞

InnoDB里的数据都是用B+树的结构组织的。

如果要删掉某个记录,InnoDB引擎只会把这个记录标记为删除,但磁盘文件的大小并不会缩小,后续有符合其前后范围的新记录插入,可能会复用这个位置。InnoDB的数据是按页存储的,如果删掉了一个数据页上的所有记录(被标记),整个数据页就可以被复用了(任意数据都可复用)。

记录的复用与数据页的复用是不同的。记录的复用,只限于符合范围条件的数据。而当整个页从B+树里面摘掉以后,可以复用到任何位置。如果相邻的两个数据页利用率都很小,系统还会把这两个页上的数据合到其中一个页上,另外一个数据页就被标记为可复用。

如果用delete命令把整个表的数据删除,所有的数据页都会被标记为可复用,但是磁盘上,文件不会变小。delete命令是不能回收表空间的,这些可以复用,而没有被使用的空间,看起来就像是空洞。


插入数据也会造成空洞。

如果数据是按照索引递增顺序插入的,那么索引是紧凑的。如果数据是随机插入的,就可能造成索引的数据页分裂。

由于一个page1满了,再插入一个page1数值大小范围内的数据时,就不得不再申请一个新的页面page2来保存数据,并将page1上的后部分数据挪到page2,页分裂完成后,page1的末尾就留下了空洞。

更新索引上的值,可以理解为删除一个旧的值,再插入一个新的值,明显也会造成空洞。经过大量增删改的表,都是可能存在空洞的。把这些空洞去掉,才能达到收缩表空间的目的。

重建表

重建表可以达到收缩表空间的目的。
在重建表的时候,InnoDB不会把整张表占满,每个页留了1/16给后续的update更新用。重建表之后不是“最”紧凑的。

删除主键或者创建主键都会将整个表重建。
更改表结构(加字段)会重建主键索引,主键索引树叶子节点的值要更新。普通索引不用重建。

新建一个与表A(原始表)结构相同的临时表B,然后按照主键ID递增的顺序,把数据一行一行地从表A里读出来再插入到表B中。交换表名,删除临时表。

MySQL5.5版本前使用alter table A engine=InnoDB;命令来重建表。

表B是新建的表,表A主键索引上的空洞,在表B中都不存在了。表B的主键索引更紧凑,数据页的利用率页更高。从效果上看,起到了收缩表A空间的作用。


DDL花时间最多的步骤是往临时表插入数据的过程,如果在这个过程中,有新的数据要写入到表A的话,就会造成数据丢失。在整个DDL过程中,表A中不能有更新,阻塞MDL,这个DDL不是Online的。

MySQL5.6版本引入了OnlineDDL:

  1. 建立一个临时文件,扫描表A主键的所有数据页;
  2. 用数据页中表A的记录生成B+树,存储到临时文件中;
  3. 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中。
  4. 临时文件生成后,将日志文件中的操作应用到临时文件(可能会引入新的空洞),得到一个逻辑数据上与表A相同的数据文件。
  5. 用临时文件替换表A的数据文件。

由于日志文件记录和重放操作这个功能的存在,在重建表的过程冲,允许对表A(旧表)做增删改操作。

alter语句(DDL操作)在启动的时候需要获取MDL写锁,在真正拷贝数据之前退化成读锁,MDL读锁不会阻塞增删改操作,同时禁止其他线程对这个表同时做DDL。最后临时文件替换表A的数据文件时重新加上MDL写锁。

Online DDL最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间可以接收增删改操作。相对于整个DDL过程来说,写锁的时间非常短,对业务来说,相当于是Online的。

重建表会扫描原表结构和构建临时文件。对于很大的表很消耗IO和CPU资源。线上服务要很小心的控制操作时间。
比较安全的操作是使用GitHub开源的gh-ost做。

Online和inplace

MySQL5.5版本重建表,把表A中的数据导出来存放在tmp_table。这是一个临时表,是在server层创建的。

alter table t engine=innodb,ALGORITHM=copy;– 强制拷贝表

MySQL5.6版本重建表,根据表A重建出来的数据是放在tmp_file里的,这个临时文件是InnoDB在内部创建出来的。整个DDL过程都在InnoDB内部完成。对于server层来说,没有把数据挪动到临时表,是一个原地操作。称之为inplace。

alter table t engine=innodb,ALGORITHM=inplace;

区别(都需要占用临时空间):

  • DDL过程如果是Online的,就一定是inplace的;
  • 反过来未必,inplace的DDL,有可能不是Online的。添加全文索引(FULLTEXT index)和空间索引(SPATIAL index)。8.0

    给InnoDB表的一个字段加全文索引:alter table t add FULLTEXT(field_name);
    这个过程是inplace的,但是会阻塞增删改操作,是非Online的。


optimize table、analyze table、alter table三种重建表区别:

  • MySQL5.6版本开始,alter table t engine=InnoDB;(recreate)默认ALGORITHM=inplace。(no-rebuild主键索引不用重建)

    在事务里面显式用alter table语句,会默认提交前面的事务,然后自己独立执行。

  • analyze table t;不是重建表,只是对表的索引信息做重新统计,没有修改数据,这个过程中加了MDL读锁。
  • optimize table t;等于recreate+analyze。(recreate后全新的表,极端情况下才需要analyze)