MySQL删除数据,表文件大小依然没变的原因

对于运行很长时间的数据库来说,往往会出现表占用存储空间过大的问题,可是将许多没用的表删除之后,表文件的大小并没有改变,想解决这个问题,就需要了解 InnoDB 如何回收表空间的。

对于一张表来说,占用空间重要分为两部分,表结构和表数据。通常来说,表结构定义占用的空间很小。所以空间的问题主要和表数据有关。

在 MySQL 8.0 前,表结构存储在以 .frm 为后缀的文件里。在 8.0,允许将表结构定义在系统数据表中。

关于表数据的存放

可以将表数据存在共享表空间,或者单独的文件中,通过 innodb_file_per_table 来控制。

  • 如果为 OFF ,表示存在系统共享表空间中,和数据字典一起
  • 如果为 ON,每个 InnoDB 表结构存储在 .idb 为后缀的文件中

在 5.6.6 以后,默认值为 ON.

建议将该参数设置为 ON,这样在不需要时,通过 drop table 命令,系统就会直接删除该文件。

但在共享表空间中,即使表删掉,空间也不会回收。

truncate = drop + create 

数据删除流程

但有时使用 delete删除数据时,仅仅删除的是某些行,但这可能就会出现表空间没有被回收的情况。

我们知道,MySQL InnoDB 中采用了 B+ 树作为存储数据的结构,也就是常说的索引组织表,并且数据时按照页来存储的。

在删除数据时,会有两种情况:

  • 删除数据页中的某些记录
  • 删除整个数据页的内容

比如想要删除 R4 这条记录:

InnoDB 直接将 R4 这条记录标记为删除,称为可复用的位置。如果之后要插入 ID 在 300 到 700 间的记录时,就会复用该位置。由此可见,磁盘文件的大小并不会减少。

而且记录的复用,只限于符合范围条件的数据。之后要插入 ID 为 800 的记录,R4 的位置就不能被复用了。

再比如要是删除了整个数据页的内容,假设删除 R3 R4 R5,为 Page A 数据页。

这时 InnoDB 就会将整个 Page A 标记为删除状态,之后整个数据都可以被复用,没有范围的限制。比如要插入 ID=50 的内容就可以直接复用。

并且如果两个相邻的数据页利用率都很小,就会把两个页中的数据合到其中一个页上,另一个页标记为可复用。

综上,无论是数据行的删除还是数据页的删除,都是将其标记为删除的状态,用于复用,所以文件并不会减小。对应到具体的操作就是使用 delete 命令.

而且,我们还可以发现,对于第一种删除记录的情况,由于复用时会有范围的限制,所以就会出现很多空隙的情况,比如删除 R4,插入的却是 ID=800.

插入操作也会造成空隙

在插入数据时,如果数据按照索引递增顺序插入,索引的结构会是紧凑的。但如果是随机插入的,很可能造成索引数据页分裂。

比如给已满的 Page A 插入数据。

由于 Page A 满了,所以要申请 Page B,调整 Page A 的过程到 Page B,这也称为页分裂。

结束后 Page A 就有了空隙。

另外对于更新操作也是,先删除再插入,也会造成空隙。

进而对于大量进行增删改的表,都有可能存在空洞。如果把空洞去掉,自然空间就被释放了。

使用重建表

为了把表中的空隙去掉,这时就可以采用重新建一个与表 A 结构相同的表 B,然后按照主键 ID 递增的顺序,把数据依次插入到 B 表中。

由于是顺序插入,自然 B 表的空隙不存在,数据页的利用率也更高。之后用表 B 代替表 A,好像起到了收缩表 A 空间的作用。

具体通过:

alter table A engine=InnoDB

在 5.5 版本后,该命令和上面提到的流程差不多,而且 MySQL 会自己完成数据,交换表名,删除旧表的操作。

但这就有一个问题,在 DDL 中,表 A 不能有更新,此时有数据写入表 A 的话,就会造成数据丢失。

在 5.6 版本后引入了 Online DDL。

Online DDL

Online DDL 在其基础上做了如下的更新:

重建表的过程如下:

  1. 建立一个临时文件,扫描表 A 主键的所有数据页。
  2. 用生成的数据页生成 B+ 树,存储到临时文件中。
  3. 生成临时文件时,如果有对 A 的操作,将其记录在日志文件中,对应图中 state 2 的状态。
  4. 临时文件生成后,将日志文件应用到临时文件中,得到与 A 表相同的数据文件,对应 state 3 状态。
  5. 用临时文件替换 A 表的数据文件。

由于 row log 日志文件存在,可以在重建表示,对表 A 进行 DML 操作。

需要注意的是,在 alter 语句执行前,会先申请 MDL 写锁,但在拷贝数据前会退化成 MDL 读锁,从而支持 DML 操作。

至于为什么不大 MDL 去掉,是防止其他线程对这个表同时做 DDL 操作。

对于大表来说,该操作很耗 IO 和 CPU 资源,所以在线上操作时,要控制操作时间。如果为了保证安全,推荐使用 gh-ost 来迁移。

Online 和 inplace

首先说一下 inplace 和 copy 的区别:

在 Online DDL 中,表 A 重建后的数据放在 tmp_file 中,这个临时文件是在 InnoDB 内部创建出来的。整个 DDL 在 InnoDB 内部完成。进而对于 Server 层来说,并没有数据移动到临时表中,是一个 "原地" 操作,所以叫 "inplace" .

而在之前普通的 DDL 中,创建后的表 A 是在 tmp_table 是 Server 创建的,所以叫 "copy"

对应到语句其实就是:

-- alter table t engine=InnoDB 默认为下面
alter table t engine=innodb,ALGORITHM=inplace;

-- 走的就是 server 拷贝的过程
alter table t engine=innodb,ALGORITHM=copy;

需要注意的是 inplace 和 Online 并不是对应关系:

  1. DDL 过程是 Online,则一定是 inplace
  2. 如果是 inplace 的 DDL 不应当是 Online,如在 <= 8.0, 添加全文索引和空间索引就属于这种情况。

拓展

说一下 optimize,analyze,alter table 三种重建表之间的区别:

  1. alter table t engine = InnoDB(也就是 recreate)默认的是 Oline DDL 过程。
  2. analyze table t 不是重建表,仅仅是对表的索引信息做重新统计,没有修改数据,期间加 MDL 读锁。
  3. optimize table t 等于上两步的操作。

在事务里面使用 alter table 默认会自动提交事务,保持事务一致性

如果有时,在重建某张表后,空间不仅没有变小,甚至还变大了一点点。这时因为,重建的这张表本身没有空隙,在 DDL 期间,刚好有一些 DML 执行,引入了一些新的空隙。

而且 InnoDB 不会把整张表填满,每个页留下 1/16 给后续的更新用,所以可能远离是紧凑的,但重建后变成的稍有空隙。

总结

现在我们知道,在使用 delete 删除数据时,其实对应的数据行并不是真正的删除,InnoDB 仅仅是将其标记成可复用的状态,所以表空间不会变小。

通常来说,在标记复用空间时分为两种,一种是仅将某些数据页中的位置标记为删除状态,但这样的位置只会在一定范围内使用,会出现空隙的情况。

另一种是将整个数据页标记成可复用的状态,这样的数据页没有限制,可直接复用。

为了解决这个问题,我们可以采用重建表的方式,其中在 5.6 版本后,创建表已经支持 Online 的操作,但最后是在业务低峰时使用

以上就是MySQL删除数据,表文件大小依然没变的原因的详细内容,更多关于MySQL表文件大小的资料请关注我们其它相关文章!

(0)

相关推荐

  • MySQL 处理重复数据的方法(防止、删除)

    有些 MySQL 数据表中可能存在重复的记录,有些情况我们允许重复数据的存在,但有时候我们也需要删除这些重复的数据. 本章节我们将为大家介绍如何防止数据表出现重复数据及如何删除数据表中的重复数据. 防止表中出现重复数据 你可以在 MySQL 数据表中设置指定的字段为 PRIMARY KEY(主键) 或者 UNIQUE(唯一) 索引来保证数据的唯一性. 让我们尝试一个实例:下表中无索引及主键,所以该表允许出现多条重复记录. CREATE TABLE person_tbl ( first_name

  • MySQL使用mysqldump+binlog完整恢复被删除的数据库原理解析

    (一)概述 在日常MySQL数据库运维过程中,可能会遇到用户误删除数据,常见的误删除数据操作有: 用户执行delete,因为条件不对,删除了不应该删除的数据(DML操作): 用户执行update,因为条件不对,更新数据出错(DML操作): 用户误删除表drop table(DDL操作): 用户误清空表truncate(DDL操作): 用户删除数据库drop database,跑路(DDL操作) -等 这些情况虽然不会经常遇到,但是遇到了,我们需要有能力将其恢复,下面讲述如何恢复. (二)恢复原理

  • mysql定时删除过期数据记录的简单方法

    1. 连接登录MySQL后,先查看MySQL是否开启了事件功能: 命令:show variables like '%sc%'; 发现event_sheduler是OFF关闭的; 2. 打开event_scheuler: 临时开启(mysql服务重启后之后失效) SET GLOBAL event_scheduler = ON; SET GLOBAL event_scheduler = 1; - 0代表关闭 永久开启 在my.cnf中的[mysqld]部分添加如下内容,然后重启mysql(mysql

  • Mysql的Binlog数据恢复:不小心删除数据库详解

    Mysql的Bin log数据恢复:不小心删除数据库 前言:因为不小心删除了测试机器上Mysql的一整个数据库Schema,因为是测试机所以没有做备份,现在通过MySQL的Bin log方式恢复到删除以前的数据库. 当然做Bin log的数据恢复前提是已经打开Bin log的功能,如果又没做数据备份,又没打开Bin log日志,那你就可能需要考虑快照等其它方式从系统的角度去恢复. Bin log 常用于数据增量备份和恢复,以及数据库主从复制.如果没有开启,可以通过如下方式打开: 1.打开mysq

  • MySQL数据误删除的快速解决方法(MySQL闪回工具)

    概述 Binlog2sql是一个Python开发开源的MySQL Binlog解析工具,能够将Binlog解析为原始的SQL,也支持将Binlog解析为回滚的SQL,去除主键的INSERT SQL,是DBA和运维人员数据恢复好帮手. 一.安装配置 1.1 用途 数据快速回滚(闪回) 主从切换后新master丢数据的修复 从binlog生成标准SQL,带来的衍生功能 支持MySQL5.6,5.7 1.2 安装 shell> git clone https://github.com/danfengc

  • MySQL对数据库操作(创建、选择、删除)

    MySQL 创建数据库 我们可以在登陆 MySQL 服务后,使用 create 命令创建数据库,语法如下: CREATE DATABASE 数据库名; 以下命令简单的演示了创建数据库的过程,数据名为 RUNOOB: [root@host]# mysql -u root -p Enter password:****** # 登录后进入终端 mysql> create DATABASE RUNOOB; 使用 mysqladmin 创建数据库 使用普通用户,你可能需要特定的权限来创建或者删除 MySQ

  • mysql数据库常见基本操作实例分析【创建、查看、修改及删除数据库】

    本文实例讲述了mysql数据库常见基本操作.分享给大家供大家参考,具体如下: 本节相关: 创建数据库 查看数据库 修改数据库 删除数据库 首发时间:2018-02-13 20:47 修改: 2018-04-07:考虑到规范化,将所有语法中"关键字"变成大写;以及因为整理"mysql学习之路",移除字符集和校对集问题并归成一个新博文. 创建数据库  : 语法  : CREATE DATABASE 数据库名字[库选项]; 库选项说明  : 库选项是可选项,可以不写 ,如

  • MySQL 快速删除大量数据(千万级别)的几种实践方案详解

    笔者最近工作中遇见一个性能瓶颈问题,MySQL表,每天大概新增776万条记录,存储周期为7天,超过7天的数据需要在新增记录前老化.连续运行9天以后,删除一天的数据大概需要3个半小时(环境:128G, 32核,4T硬盘),而这是不能接受的.当然如果要整个表删除,毋庸置疑用 TRUNCATE TABLE就好. 最初的方案(因为未预料到删除会如此慢),代码如下(最简单和朴素的方法): delete from table_name where cnt_date <= target_date 后经过研究,

  • MySQL删除数据库的两种方法

    本文为大家分享了两种MySQL删除数据库的方法,供大家参考,具体内容如下 第一种方法:使用 mysqladmin 删除数据库 使用普通用户登陆mysql服务器,你可能需要特定的权限来创建或者删除 MySQL 数据库. 所以我们这边使用root用户登录,root用户拥有最高权限,可以使用 mysql mysqladmin 命令来创建数据库. 在删除数据库过程中,务必要十分谨慎,因为在执行删除命令后,所有数据将会消失. 以下实例删除数据库TUTORIALS(该数据库在前一章节已创建): [root@

  • linux定时备份MySQL数据库并删除以前的备份文件(推荐)

    备份数据库,这是必须会用到的,今天上午花了几个小时整理了一下,结果不错.下面详细的走一遍 一丶选择一个比较大位置来保存备份文件(创建文件就行) 使用mkdir来创建文件夹,这个简单. 二丶检查有没安装crond,如果没有,先安装 rpm -qa|grep cron  安装后会是 一般没有安装,所以先 yum -y install vixie-cron 然后再 yum -y install crontabs 这样服务就安装好了. 三丶写备份脚本 vi  /var/spool/cron/name.s

  • MySQL实现快速删除所有表而不删除数据库的方法

    本文实例讲述了MySQL实现快速删除所有表而不删除数据库的方法.分享给大家供大家参考,具体如下: 如果直接使用phpmyadmin操作的话肯定非常简单,勾选数据表->点击删除->点击确定,操作完毕! 这里介绍一下快速删除数据表的SQL命令操作方法. 删除表的命令: drop table 表名; 如果有200张表,执行200次,想想就不想动手了. 下面提供一个使用information_schema库的方案: 复制代码 代码如下: SELECT CONCAT('drop table ',tabl

  • Linux实现定时备份MySQL数据库并删除30天前的备份文件

    1. MySQL5.6以上版本 2. 修改 /etc/my.cnf 文件 # vim /etc/my.cnf [client] host=localhost user=你的数据库用户 password='你的数据库密码' 3. 编写数据库脚本 mysql-backup.sh # vim mysql-backup.sh #!/bin/bash backupDir=数据库备份目录 backupTime=`date +%Y%m%d%H%M%S` mysqldump 你的数据库 | gzip > $ba

随机推荐