思考一下:为什么又的时候,将表中的数据删掉了一半,但是表文件的大小还是没变?
一个InnoDB表包含两个部分,即:表结构定义和数据,表结构定义占用的空间很小,今天主要讨论的是表数据。
参数innodb_file_per_table
innodb_file_per_table参数控制表数据在共享表空间还是单独的文件;
参数设置为OFF,表示表的数据放在系统共享表空间,跟数据字典在一起;
参数设置为ON,表示每个InnoDB表数据存储在一个以.ibd为后缀的文件中;
默认为ON,一个表单独存储为一个文件更容易管理,直接通过drop table命令,系统可以直接删除。
但是要解决表中的数据被删除,但是表空间还未回收的问题的话,先了解一下删除的流程.
数据删除流程

假如现在要删除R4的这个记录,InnoDB引擎会把R4这个记录标记为删除,再插入一个ID在300和600之间的记录的时候,可能会复用这个位置,但是磁盘文件大小并不会缩小。
如果删除一个数据页上所有的数据,那么整个数据页都是可以复用的;数据页的复用和记录的复用是不同的,记录的复用只限于符合范围条件的复用;
delete命令其实只是把记录的位置或者数据页标记为了"可复用",但是磁盘文件的大小是不会改变的,delete不能回收表空间,可以复用,没有使用的空间看起来像是一个“黑洞”
不仅删除,经过大量增删改的表,都是可能存在数据空洞的,如果能够去除这些空洞就能达到收缩表空间的目的。
重建表
如何删除表中的空洞,进行空间收缩呢?
重建一个与表A结构相同的表B,按照主键ID递增的顺序,将数据一行一行的从表A中读出来再插入表B中;
表A主键索引上的空洞,在表B中就不存在了,表B的主键更加紧凑,数据页的利用率更高,表B作为临时表,数据从A导入B以后,再用表B替换表A。可以使用alter table A engine = InnoDB命令来重建表。

在MySQL5.6版本以后,开始引入Online DDL,对这个操作流程进行了优化,重建表的流程:
- 建立一个临时文件,扫描表A主键的所有数据页;
- 用数据页中表A的记录生成B+树,存储在临时文件中;
- 生成临时文件的过程中,将所有对A的操作都记录到一个日志文件(row log)中,对应于图2中的state2;
- 临时文件生成后,将日志文件的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,就是state3的状态‘
- 用临时文件替代表A的数据文件;

可以看到,与上图中不同的是,这个方案在重建表的过程中,允许对表A做增删改查操作;对于一个大表来说,OnlineDDL最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间是可以接收增删改操作。
Online和inplace
图一中,表A导出来的数据存放位置是tmp_table,这个临时表,是在server层中创建;
图二中,表A重建出来的数据是放在“tmp_file”里面的,这个文件是InnoDB内部创建出来的,整个DDL过程都在InnoDB中完成;对于server层来说,没有把数据挪到临时表,是一个“原地操作”,所以是inplace;
alter table t engine=innodb,ALGORITHM=inplace;
跟inplace对应的拷贝表的方式,用法是:
alter table t engine = innodb,ALGORITHM=copy;
当ALGORITHM=copy的时候,表示的是强制拷贝表,对应流程就是图3的操作过程;
如果要给InnoDB表的一个字段加全文索引,写法是:
alter table t add FULLTEXT(fileld_name);
这个过程是inplace的,但是会阻塞增删改操作,是非online的
Online和inplace两个逻辑之间的关系:
- DDL过程如果是Online的,就一定是inplace的;
- inplace的DDL,也可能不是online的;
总结
如果要收缩一个表,只是delete表中不用的数据,表文件大小是不会改变的,还要通过alter table命令重建表,才能达到缩小文件的目的;
重建表有两种方式,Online DDL的方式是可以考虑在业务低峰期使用的;