MYSQL笔记4:索引之数据删除

140 阅读4分钟

上一篇笔记中介绍了InnoDB的索引结构,那么当删除数据时索引树会会有什么变化那?

通过delete删除数据记录时,在索引文件中对应在记录也需要删除。当将记录从索引文件中删除时只是将索引所在的位置标记为删除,后续有数据插入到相同的位置时,可能会直接复用这个位置。例如有如下索引文件:

image.png 当删掉 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 阶段:

  1. 获取表的排它锁,阻塞其它DML语句的读写。
  2. 创建临时文件。
  3. 分配row_log空间,用于记录后续DDL执行期间的DML语句。

二、  DDL执行阶段:

  1. 将排它锁降级为DML的读锁,DML的读锁不阻塞数据增删查改,但会阻止其它线程修改表结构。
  2. 用原表中的记录生成新B+树,记录到临时文件中。
  3. 在DDL执行阶段的的DML语句记录到row_log中。

三、commit阶段:

  1. 升级锁为排它锁,阻塞其它DML语句的读写。
  2. 将row_log中的临时操作应用到临时文件中。
  3. 重命名临时文件来替换原表的数据文件,然后将原表的文件删除。

从上面过程中可以看出,其中耗时最近的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;