Mysql学习笔记--收缩表

338 阅读6分钟

思考

经常会遇见删除表数据后,表文件大小不变。

1.在 MySQL 8.0 版本以前,表结构是存在以.frm 为后缀的文件里。而 MySQL 8.0 版本,则已经允许把表结构定义放在系统数据表中了。因为表结构定义占用的空间很小,所以问题出现在表数据。

2.表数据既可以存在共享表空间也可以存在单独的文件中,这个行为由参数innodb_file_per_table控制

1.这个参数设置为 OFF 表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起;

2.这个参数设置为 ON 表示的是,每个 InnoDB 表数据存储在一个以 .ibd 为后缀的文件中。

删除表的时候使用drop,可以用于表空间回收

drop table

但是删除数据的时候并没有造成表空间回收

3.删除流程

Innodb引擎是一种B+树结构,覆草图一张。

假设我们删除500的位置,其实删除的并不是空间而是删除500空间位置的标记,要再插入一个 ID 在 300 和 600 之间的记录时,可能会复用这个位置。但是,磁盘文件的大小并不会缩小。

**4.**但是,数据页的复用跟记录的复用是不同的。

**1.记录的复用,只限于符合范围条件的数据。比如上面的这个例子,R4 这条记录被删除后,如果插入一个 ID 是 400 的行,可以直接复用这个空间。但如果插入的是一个 ID 是 800 的行,就不能复用这个位置了。
**

2.整个页被标记为删除,这个页可以被复用到任何位置,没有范围限制。比如此时插入一条id为30的数据也可以复用数据页

3.如果相邻的两个数据页利用率都很小,系统就会把这两个页上的数据合到其中一个页上,另外一个数据页就被标记为可复用。(此时另外一个数据页被插入其他数据时候就可以复用这个数据页的空间了)

5.所以delete删除数据,只是记录数据的位置或者数据页标记为可复用,其实空间并没有改变.

6.通过 delete 命令是不能回收表空间的。这些可以复用,而没有被使用的空间,看起来就像是“空洞”

实践

查看下users这张表的占用空间

select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables where table_schema='zerg' and table_name='users';

结果:

删除里面一条数据

DELETE FROM `users` WHERE (`id`='4')

结果:

删除所有数据

DELETE FROM `users`

结果:

实际上不仅仅是删除语句可以造成空洞,插入也会造成空洞

**可以看到,由于 page A 满了,再插入一个 ID 是 550 的数据时,就不得不再申请一个新的页面 page B 来保存数据了。页分裂完成后,page A 的末尾就留下了空洞(注意:实际上,可能不止 1 个记录的位置是空洞)。
**

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

9.更新索引上的值可以理解为删除一个旧值,更新一个新值(说再详细些应该是这样,其实更新操作,主键索引树的位置是不会变的,可能会变的是哪些二级索引树。)

10.所以经历过大量增删改查的表可能出现大量空洞,如何收缩这些表就需要重建表操作。

重建表

1.重建表的流程就相当于创建一个与表A相同结构的表,然后按照递增的顺序将主键ID进行递增,把表A的数据一行一行插入进表B,由于表B是新建的表,表A上面的空洞在表B上面就不会发生,从效果上起到收缩表的作用

alter table A engine=InnoDB

**在 MySQL 5.5 版本之前,这个命令的执行流程跟我们前面描述的差不多,区别只是这个临时表 B 不需要你自己创建,MySQL 会自动完成转存数据、交换表名、删除旧表的操作。
**

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

2.不过在Mysql5.6以后开始引入Online DDL

1.建立一个临时文件,扫描表 A 主键的所有数据页;

2.用数据页中表 A 的记录生成 B+ 树,存储到临时文件中;生成临时文件的过程中,将所有对 A 的操作记录在一个日志文件(row log)中,对应的是图中 state2 的状态;

3.临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件,对应的就是图中 state3 的状态;

4.用临时文件替换表 A 的数据文件。

其实就是可以在创建临时表的时候可以插入数据了。

Online DDL 其实是会先获取MDL写锁, 再退化成MDL读锁;但MDL写锁持有时间比较短,所以可以称为Online; 而MDL读锁,不阻止数据增删查改,但会阻止其它线程修改表结构

一定使用锁的原因是因为防止其他线程也执行DDL

(DML加读锁 DDL加写锁,读锁之间不互斥,多个线程可以对同一张表进行增删改查,读写锁之间,写锁之间互斥

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

对于很大的表来说,这个操作是很消耗 IO 和 CPU 资源的。因此,如果是线上服务,你要很小心地控制操作时间。

Online和inplace

1.inplace原地的意思

2.上图中的临时表是Server层建立的,而5.6以后版本临时表是Innodb内部创建的(没有把表移动到临时文件中,所以用inplace原地的这个单词)

alter table t engine=innodb,ALGORITHM=inplace;

3. 5.6之前重建表的替换代码

alter table t engine=innodb,ALGORITHM=copy;

DDL 过程如果是 Online 的,就一定是 inplace 的;反过来未必,也就是说 inplace 的 DDL,有可能不是 Online 的。截止到 MySQL 8.0,添加全文索引(FULLTEXT index)和空间索引 (SPATIAL index) 就属于这种情况。

结论

1.如果要收缩一个表,只是 delete 掉表里面不用的数据的话,表文件的大小是不会变的

2.还要通过 alter table 命令重建表,才能达到表文件变小的目的。重建表的两种实现方式,Online DDL 的方式是可以考虑在业务低峰期使用的,而 MySQL 5.5 及之前的版本,这个命令是会阻塞 DML 。