MySQL-24丨安全高效地删除大量无用数据

Posted by jiefang on November 1, 2019

安全高效地删除大量无用数据

共享表空间和独立表空间

InnoDB 数据是按照表空间进行存放的,其表空间分为共享表空间和独立表空间。

共享表空间

共享表空间:表的数据放在系统共享表空间,也就是跟数据字典放一起。文件名为 ibdata1。可以通过参数 innodb_data_file_path 进行设置。在 my.cnf 中配置,如下:

[mysqld]
innodb_data_file_path = ibdata1:1G;ibdata2:1G:autoextend

表示用两个文件(ibdata1 和 ibdata2)组成表空间,文件 ibdata1 的大小为 1G,文件 ibdata2 的大小为 1G,autoextend 表示用完 1G 可以自动增长。

独立表空间

独立表空间:每个 InnoDB 表数据存储在一个以 .idb 为后缀的文件中。

由参数 innodb_file_per_table 控制。

  • 设置为 on 表示使用独立表空间;
  • 设置为 off 表示使用共享表空间。

建议设置为独立表空间,原因是:如果某张表被drop掉,会直接删除该表对应的文件,如果放在共享表空间中,即使执行了 drop table 操作,空间还是不能回收。

几种数据删除形式

删除表

drop表步骤:

  • 首先将表名改为t29_bak_20191011;

    alter table t29 rename t29_bak_20191011;

  • 然后等待半个月,观察是否有程序因为找不到表报错;
  • 如果没有跟表 t29 相关报错,则直接 drop 掉 t29_bak_20191011;

    drop table t29_bak_20191011;

清空表

某张表历史数据不需要使用了,要做一次清空,则可以考虑使用 truncate。 建议步骤:

  • 创建一张与 t29 表结构相同的临时表:

    create table t29_bak_20191011 like t29;

  • 数据拷贝到临时表:

    insert into t29_bak_20191011 select * from t29;

  • 清空该表:

    truncate table t29;

需要清空表而使用 delete from table_name,导致主从延迟和磁盘 IO 跑满的情况。 原因是 binlog 为行模式的情况下,执行全表 delete 会生成每一行对应的删除操作,因此可能导致单个删除事务非常大。而 truncate 可以理解为 drop + create,在 binlog 为 row 模式的情况下,也只会产生一行 truncate 操作。所以,建议清空表时使用 truncate 而不使用 delete。

非分区表删除部分记录

有时发现,在 delete 很多数据后,实际表文件大小没变化。这是什么原因呢?

原因是,如果通过 delete 删除某条记录,InnoDB 引擎会把这条记录标记为删除,但是磁盘文件的大小并不会缩小。如果之后要在这中间插入一条数据,则可以复用这个位置,如果一直没有数据插入,就会形成一个 “空洞”。因此 delete 命令是不能回收空间的,这也是 delete 后表文件大小没变化的原因。

对于非分区表删除部分记录(比如删除 2017 年之前的数据,语句为:delete from table_name where date<‘2017-01-01’;),建议的步骤是:

  • 首先备份全表;
  • 确保 date 字段有索引,如果没有索引,则需要添加索引(目的是避免执行删除命令时,全表扫描);
  • 如果要删除的数据比较多,建议写一个循环,每次删除满足条件记录的 1000 条(目的是避免大事务),删完为止:

    delete from table_name where date<’2017-01-01’ limit 1000;

  • 最后重建表(目的是释放表空间,但是会锁表,建议在业务低峰执行):

    alter table table_name engine=InnoDB;
    或者
    optimize table student;

分区表删除部分分区

删除某一分区的数据:

alter table t29_log drop partition p2016;

对于要经常删除历史数据的表,建议配置成分区表。以方便后续历史数据删除。