mysql学习 实践(5) 为什么表数据删掉一半,表文件大小不变?
总结
1、参数 innodb_file_per_table 是什么意思?
答:表数据既可以存在共享表空间里,也可以是单独的文件。这个行为是由参数 innodb_file_per_table 控制的:
- 这个参数设置为 OFF 表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起;
- 这个参数设置为 ON 表示的是,每个 InnoDB 表数据存储在一个以 .ibd 为后缀的文件中。
从 MySQL 5.6.6 版本开始,它的默认值就是 ON 了。
我建议你不论使用 MySQL 的哪个版本,都将这个值设置为 ON。因为,一个表单独存储为一个文件更容易管理,而且在你不需要这个表的时候,通过 drop table 命令,系统就会直接删除这个文件。而如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。
所以,将 innodb_file_per_table 设置为 ON,是推荐做法,我们接下来的讨论都是基于这个设置展开的。
2、表的数据信息存在哪里?
答:表数据信息可能较小也可能巨大无比,她可以存储在共享表空间里,也可以单独存储在一个以.ibd为后缀的文件里,由参数innodb_file_per_table来控制,建议总是作为一个单独的文件来存储,这样非常容易管理,并且在不需要的时候,使用drop table命令也能直接把对应的文件删除,如果存储在共享空间之中即使表删除了空间也不会释放。
3、表的结构信息存在哪里?
答:首先,表结构定义占有的存储空间比较小,在MySQL8.0之前,表结构的定义信息存在以.frm为后缀的文件里,在MySQL8.0之后,则允许把表结构的定义信息存在系统数据表之中。
系统数据表,主要用于存储MySQL的系统数据,比如:数据字典、undo log(默认)等文件
4、为啥删除了表的一半数8据,表文件大小没变化?
答:因为delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也可以认为是一种逻辑删除,所以物理空间没有实际释放,只是标记为可复用,表文件的大小当然是不变的!
5、空洞是啥?空洞怎么产生的?
答:空洞是被标记可复用的空间。
- 使用delete命令删除数据会产生空洞,标记为可复用
- 插入新的数据可能引起页分裂,也可能产生空洞
- 修改操作,有时是一种先删后插的动作也可能产生空
索引非递增导致页分裂,容易产生空洞。
6、如何才能删除表数据后,表文件大小就变小?
答:重建表,消除表因为进行大量的增删改操作而产生的空洞,使用如下命令:
- 从 MySQL 5.6 版本开始,alter table t engine = InnoDB(也就是 recreate);analyze table t 其实不是重建表,只是对表的索引信息做重新统计,没有修改数据,这个过程中加了 MDL 读锁;
- optimize table t 等于 recreate+analyze。
- truntace table t (等于drop+create)
7、重建表的过程
答:试想一下,如果你现在有一个表 A,需要做空间收缩,为了把表中存在的空洞去掉,你可以怎么做呢?
你可以新建一个与表 A 结构相同的表 B,然后按照主键 ID 递增的顺序,把数据一行一行地从表 A 里读出来再插入到表 B 中。
由于表 B 是新建的表,所以表 A 主键索引上的空洞,在表 B 中就都不存在了。显然地,表 B 的主键索引更紧凑,数据页的利用率也更高。如果我们把表 B 作为临时表,数据从表 A 导入表 B 的操作完成后,用表 B 替换 A,从效果上看,就起到了收缩表 A 空间的作用。
这里,你可以使用 alter table A engine=InnoDB 命令来重建表。
在 MySQL 5.5 版本之前,这个命令的执行流程跟我们前面描述的差不多,区别只是这个临时表 B 不需要你自己创建,MySQL 会自动完成转存数据、交换表名、删除旧表的操作。
图 3 改锁表 DDL
显然,花时间最多的步骤是往临时表插入数据的过程,如果在这个过程中,有新的数据要写入到表 A 的话,就会造成数据丢失。因此,在整个 DDL 过程中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。
而在 MySQL 5.6 版本开始引入的 Online DDL,对这个操作流程做了优化。
我给你简单描述一下引入了 Online DDL 之后,重建表的流程:
- 建立一个临时文件,扫描表 A 主键的所有数据页;
- 用数据页中表 A 的记录生成 B+ 树,存储到临时文件中;
- 生成临时文件的过程中,将所有对 A 的操作记录在一个日志文件(row log)中,对应的是图中 state2 的状态;
- 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件,对应的就是图中 state3 的状态;
- 用临时文件替换表 A 的数据文件。
图 4 Online DDL
可以看到,与图 3 过程的不同之处在于,由于日志文件记录和重放操作这个功能的存在,这个方案在重建表的过程中,允许对表 A 做增删改操作。这也就是 Online DDL 名字的来源。
一个新的问题,DDL 之前是要拿 MDL 写锁的,这样还能叫 Online DDL 吗?
确实,图 4 的流程中,alter 语句在启动的时候需要获取 MDL 写锁,但是这个写锁在真正拷贝数据之前就退化成读锁了。为什么要退化呢?为了实现 Online,MDL 读锁不会阻塞增删改操作。
那为什么不干脆直接解锁呢?为了保护自己,禁止其他线程对这个表同时做 DDL。
而对于一个大表来说,Online DDL 最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间可以接受增删改操作。所以,相对于整个 DDL 过程来说,锁的时间非常短。对业务来说,就可以认为是 Online 的。
需要补充说明的是,上述的这些重建方法都会扫描原表数据和构建临时文件。对于很大的表来说,这个操作是很消耗 IO 和 CPU 资源的。因此,如果是线上服务,你要很小心地控制操作时间。如果想要比较安全的操作的话,我推荐你使用 GitHub 开源的 gh-ost 来做。
8、Online 和 inplace
澄清一下它和另一个跟 DDL 有关的、容易混淆的概念 inplace 的区别。
你可能注意到了,在图 3 中,我们把表 A 中的数据导出来的存放位置叫作 tmp_table。这是一个临时表,是在 server 层创建的。
在图 4 中,根据表 A 重建出来的数据是放在“tmp_file”里的,这个临时文件是 InnoDB 在内部创建出来的。整个 DDL 过程都在 InnoDB 内部完成。对于 server 层来说,没有把数据挪动到临时表,是一个“原地”操作,这就是“inplace”名称的来源。
所以,我现在问你,如果你有一个 1TB 的表,现在磁盘间是 1.2TB,能不能做一个 inplace 的 DDL 呢?
答案是不能。因为,tmp_file 也是要占用临时空间的。
我们重建表的这个语句 alter table t engine=InnoDB,其实隐含的意思是:
1 alter table t engine=innodb,ALGORITHM=inplace;
跟 inplace 对应的就是拷贝表的方式了,用法是:
1 alter table t engine=innodb,ALGORITHM=copy;
当你使用 ALGORITHM=copy 的时候,表示的是强制拷贝表,对应的流程就是图 3 的操作过程。
但我这样说你可能会觉得,inplace 跟 Online 是不是就是一个意思?
其实不是的,只是在重建表这个逻辑中刚好是这样而已。
比如,如果我要给 InnoDB 表的一个字段加全文索引,写法是:
1 alter table t add FULLTEXT(field_name);
这个过程是 inplace 的,但会阻塞增删改操作,是非 Online 的。
如果说这两个逻辑之间的关系是什么的话,可以概括为:
- DDL 过程如果是 Online 的,就一定是 inplace 的;
- 反过来未必,也就是说 inplace 的 DDL,有可能不是 Online 的。截止到 MySQL 8.0,添加全文索引(FULLTEXT index)和空间索引 (SPATIAL index) 就属于这种情况。
思考题
1、分布式ID(雪花算法生成的ID)生成的索引会比自增长的ID性能低吗?雪花算法生成的ID是越来越大的,但不是逐渐递增,长度用的的bitint
答:性能一样的,没有一定要“连续”,只要是递增
2、不影响增删改,就是 Online;相对 Server层没有新建临时表,就是 inplace,这里怎么判断是不是相对 Server 层没有新建临时表?
答:一个最直观的判断方法是看命令执行后影响的行数,没有新建临时表的话新建的行数是0。
3、online 和 Inplace 的区别
答:MySQL各版本,对于add Index的处理方式是不同的,主要有三种:
(1)Copy Table方式
这是InnoDB最早支持的创建索引的方式。顾名思义,创建索引是通过临时表拷贝的方式实现的。
新建一个带有新索引的临时表,将原表数据全部拷贝到临时表,然后Rename,完成创建索引的操作。
这个方式创建索引,创建过程中,原表是可读的。但是会消耗一倍的存储空间。
(2)Inplace方式
这是原生MySQL 5.5,以及innodb_plugin中提供的创建索引的方式。所谓Inplace,也就是索引创建在原表上直接进行,不会拷贝临时表。相对于Copy Table方式,这是一个进步。
Inplace方式创建索引,创建过程中,原表同样可读的,但是不可写。
(3)Online方式
这是MySQL 5.6.7中提供的创建索引的方式。无论是Copy Table方式,还是Inplace方式,创建索引的过程中,原表只能允许读取,不可写。对应用有较大的限制,因此MySQL最新版本中,InnoDB支持了所谓的Online方式创建索引。
InnoDB的Online Add Index,首先是Inplace方式创建索引,无需使用临时表。在遍历聚簇索引,收集记录并插入到新索引的过程中,原表记录可修改。而修改的记录保存在Row Log中。当聚簇索引遍历完毕,并全部插入到新索引之后,重放Row Log中的记录修改,使得新索引与聚簇索引记录达到一致状态。
与Copy Table方式相比,Online Add Index采用的是Inplace方式,无需Copy Table,减少了空间开销;与此同时,Online Add Index只有在重放Row Log最后一个Block时锁表,减少了锁表的时间。
与Inplace方式相比,Online Add Index吸收了Inplace方式的优势,却减少了锁表的时间。
4、假设现在有人碰到了一个“想要收缩表空间,结果适得其反”的情况,看上去是这样的:
- 一个表 t 文件大小为 1TB;
- 对这个表执行 alter table t engine=InnoDB;
- 发现执行完成后,空间不仅没变小,还稍微大了一点儿,比如变成了 1.01TB。
你觉得可能是什么原因呢 ?
答:一个点,就是这个表,本身就已经没有空洞的了,比如说刚刚做过一次重建表操作。
在 DDL 期间,如果刚好有外部的 DML 在执行,这期间可能会引入一些新的空洞。
另一个更深刻的机制,是我们在文章中没说的。
在重建表的时候,InnoDB 不会把整张表占满,每个页留了 1/16 给后续的更新用。也就是说,其实重建表之后不是“最”紧凑的。
假如是这么一个过程:
- 将表 t 重建一次;
- 插入一部分数据,但是插入的这些数据,用掉了一部分的预留空间;
这种情况下,再重建一次表 t,就可能会出现问题中的现象。