「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
突然有一天,公司的DBA发消息说,我负责的一个系统 ,数据库所在的服务器磁盘空间以及快不够了,需要将历史数据归档,因为我这个数据库里存的是财务相关的明细数据,从公司到现在的所有数据都全部存放在里面的,一直都没有做过归档操作。后来跟DBA沟通,将里面的数据按年的方式进行归档,不同的年存放到不同表里面去,因为涉及的业务条数比较广,财务的取数最多会按照年来取数,所以就一年放一个表,这样的话,然后将去年之前的数据归档到另外的数据库里去,这样的话,那个历史库不会发生数据的修改操作,DBA那边全量本分的时候,只需要在最开始的时候备份一次就行,后面不需要在定时备份,可以减少全量备份带来的额外性能损耗。 跟DBA沟通好方案后,将归档的SQL整理后发给DBA,DBA一顿操作后,数据归档完毕,这个时候一看:这磁盘的空间并没有减少,还是原来的那个大小,带着疑惑问了下DBA:这为什么数据在原表中删除了,磁盘文件咋没有减少呢?DBA:发了个微笑表情,然后问了下:哪个时候的数据写入量比较少,他做一下重建表,就可以了。第二天早上起来的时候,再次查看磁盘空间,一下就腾出了一大部分磁盘。 通过这个归档事情,有两个问题:
- 数据删除后,为什么磁盘文件大小没有减少?
- 重建表是什么意思?为什么磁盘文件会减少?
数据删除,磁盘文件为什么不减少
InnoDB在存储数据的时候,采用的是B+树来进行组织数据结构的,但是在真正执行删除的时候,InnoDB并没有把数据从磁盘上真正的删除,而是将该磁盘文件标记为已删除,表示该磁盘空间可以复用,如果在下次插入数据的时候,需要插入到该磁盘未知的时候,就复用原来的空间,所以这样操作的话,磁盘的空间大小就不会缩小。 如果InnoDB删除数据后,就将删除的数据空间占用的话,就会引发数据页的磁盘数据移动,这样操作,会涉及到大量的磁盘IO操作,会造成很大的性能抖动,所以就采用将数据标记为删除,不用造成性能抖动,这样不好的情况会造成数据空洞。 不止删除的时候会造成数据空洞,插入的时候也会造成数据空洞,在一个数据页最后尾部需要插入数据的时候,尾部剩余的空间没法存下这条数据,这个时候就会重新开辟一个新的数据页来存储这条数据,那么上一个数据页尾部的空间,就会留着。 一张表在经过大量的增删改操作后,一定会对磁盘造成很大的空洞,为了减少空洞,就需要将那数据紧密排列在一起,从而达到收缩表的目的。
重建表操作
InnoDB自己本身都比较支持重建表的功能,只需要执行下面的语句就会完成重建表的操作。
alter table xxx engine=InnoDB;
如果数据库不支持重建表操作的话,我们自己做的时候我认为有两种方案:
- 重新拉取一个从库,从当前库中将数据同步到从库中,从库在写入数据的时候就会将数据紧密写入,自动过滤掉那些被标记为删除的数据,从而达到收缩表的要求,待主从两边的数据一致后,将从库提升为主库,然后回收掉回来的老主库,大致的流程如下图所示:
- 在先有的库中,新建一张临时表,然后将历史的数据插入到临时表,等到历史数据插入临时表后,用临时表跟老表替换,然后删除掉老表,但是这种方案,如果我们自己操作的话,很难保证临时表的增量操作跟历史表的数据一致性,可行性低于上面的那种方案。
在数据库重建表的时候采用的是第二种操作方案,自动完成数据的转存,替换表名,删除旧表名等操作,重建表的流程有下面的几个流程:
- 创建一个临时表,扫描同步表的所有数据页同步到临时表中
- 在同步数据的时候,同步表会发生数据的操作,为了不影响业务,又要保证数据的准确性,在这同步期间,会将对同步表的操作都记录到日志文件中
- 等到临时表将同步表中的数据同步完成后,在将这期间发生修改的操作同步到历史表,这样的话,临时表跟同步表的数据就一致
- 用临时表去替换同步表,然后删除同步表,这样就完成了重建表的功能