上一篇笔记中介绍了InnoDB的索引结构,那么当删除数据时索引树会会有什么变化那?
通过delete删除数据记录时,在索引文件中对应在记录也需要删除。当将记录从索引文件中删除时只是将索引所在的位置标记为删除,后续有数据插入到相同的位置时,可能会直接复用这个位置。例如有如下索引文件:
当删掉 R4 这个记录,InnoDB 引擎只会把 R4 这个记录标记为删除。如果之后要再插入一个ID在 500 和 700 之间的记录时,可能会复用这个位置。
若整个页上的数据都被删除了,那么这个页首先会被从索引树中删除,然后这个页被标记为可复用。后续插入新的记录时,若需要新的页那么可以直接复用页。
同时若两个相邻的页的利用率都很小的话,InnoDB也会将两个页的数据合并到一个页上,然后将另外一个页标记为可复用。
可以看到删除表中的记录时,索引的磁盘文件的大小并不会缩小。即使删除的是整个表中的数据,InnoDB也只是将所有的页标记为可复用,磁盘文件的大小并不会变小。也就是delete 命令是不能回收表空间的。
重建表
无论是删除数据还是插入数据都会导致索引文件中出现‘空洞’,若要把索引文件中的‘空洞’都去除,那么就需要重建表。
例如有表A,那先么创建一个与表A结构相同的临时表B,然后按照主键ID递增的顺序,把数据一行一行地从表A里读出来再插入到表B中。表B是新建的表且数据都是按主键ID顺序插入的,所以B中自然不存在数据空洞。最后再用表B替换表A,并删除旧表。
以上操作并不需要我们手动去完成,MySQL中通过‘alter table A engine=InnoDB’命令即可重建表。
但在MySQL 5.5之前alter table的操作都是通过类似以上的Copy Table的方式来完成的,这个过程中只能读不能写,这会导致业务的严重阻塞。
在MySQL 5.6中开始InnoDB支持了重建表的Online DDL,大大减少了重建表过程中的阻塞时间。InnoDB 的Online DDL主要分为prepare 阶段、DDL执行阶段和Commit阶段,重建表的过程如下:
一、 prepare 阶段:
- 获取表的排它锁,阻塞其它DML语句的读写。
- 创建临时文件。
- 分配row_log空间,用于记录后续DDL执行期间的DML语句。
二、 DDL执行阶段:
- 将排它锁降级为DML的读锁,DML的读锁不阻塞数据增删查改,但会阻止其它线程修改表结构。
- 用原表中的记录生成新B+树,记录到临时文件中。
- 在DDL执行阶段的的DML语句记录到row_log中。
三、commit阶段:
- 升级锁为排它锁,阻塞其它DML语句的读写。
- 将row_log中的临时操作应用到临时文件中。
- 重命名临时文件来替换原表的数据文件,然后将原表的文件删除。
从上面过程中可以看出,其中耗时最近的DDL执行阶段时可以接受DML的读写,只有在prepare阶段和commit阶段会短暂的添加排他锁。
虽然DDL执行阶段不会阻塞DML的读写,但若原表中的数据非常大的话,拷贝数据的过程建会占用大量的IO和内存资源,这时需要小心的控制操作时间,尽量选择业务比较空闲的时间来操作。
还需要注意重建表的操作是由InnoDB来完成的,InnoDB通过临时文件而非临时表来重建表,这个过程在Server层看来并没有数据挪动到临时表,是一个“原地”操作,所以Server层将其视为inplace online DDL,而这也是“inplace”名称的来源。‘alter table t engine=InnoDB’,其实隐含的意思是:
alter table t engine=innodb,ALGORITHM=inplace;