在完成一个分表项目后,发现分表的数据迁移后,新库所需的存储容量远大于本来两张表的大小。在作了一番查询了解后,完成了优化。mysql
回过头来,须要进一步了解下为何会出现这样的状况。git
与标题的问题的相似问题还有,为何表数据内容删除了而表大小没有变化。其本质都是同样的。github
要回答这些问题,咱们须要从mysql的索引模型谈起。面试
因为 InnoDB 存储引擎在 MySQL 数据库中使用最为普遍,因此接下来就以 InnoDB 为例,分析其中的索引模型。算法
在 InnoDB 中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。而InnoDB中,使用了 B+ 树索引模型,因此数据都是存储在 B+ 树中的,每个索引会对应一颗B+树。sql
假设,咱们有一个主键列为 ID 的表,表中有字段 k,而且在 k 上有索引,建表语句以下数据库
CREATE TABLE `t` (性能
`id` int(11) NOT NULL,优化
`k` int(11) NOT NULL,线程
`name` varchar(16) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
表中 R1~R5 的 (ID,k) 值分别为 (10,1)、(20,2)、(30,3)、(50,5) 和 (70,7),索引id和索引k的B+树的示例示意图以下。
根据叶子节点的内容,索引类型分为主键索引和非主键索引,主键索引的叶子节点存的是整行数据R1~R5,非主键索引的叶子节点内容是主键的值。
从图中能够看出,基于非主键索引的查询须要多扫描一棵索引树才能找到对应的数据。
提一句题外话,咱们在应用中应该尽可能使用主键查询。
B+ 树为了维护索引有序性,在增删改数据的时候须要作必要的维护。
假设,咱们要删掉 R4 这个记录,InnoDB 引擎只会把 R4 这个记录标记为删除。若是以后要再插入一个 ID 在 300 和 600 之间的记录时,可能会复用这个位置。
若是删掉了一个数据页上的全部记录,那么整个数据页就能被复用了。进一步地,若是咱们用 delete 命令把整个表的数据删除呢?结果就是,这个表相关的全部的数据页都会被标记为可复用。
可是,不管如何,磁盘文件的大小并不会缩小。
这些被标记为可复用,而并无实际被使用的空间,就是一些“存储空洞”。
实际上,不止是删除数据会形成空洞,插入数据也会。
以上图为例,若是插入新的行 ID 值为 80,则只须要在 R5 的记录后面插入一个新记录。
若是新插入的 ID 值为 60,就相对麻烦了,须要逻辑上挪动后面的数据,空出位置。
而更糟的状况是,若是 R5 所在的数据页已经满了,根据 B+ 树的算法,这时候须要申请一个新的数据页,而后挪动部分数据过去。这个过程称为页分裂。在这种状况下,性能天然会受影响。
除了性能外,页分裂操做还影响数据页的利用率。本来放在一个页的数据,如今分到两个页中,插入一条记录居然使得总体空间利用率下降大约 50%。
能够看到,因为 page 2 满了,再插入一个 ID 是 60 的数据时,就不得再也不申请一个新的页面 page 3 来保存数据了。
页分裂完成后,page 2 的末尾就留下了空洞(注意:实际上,可能不止 1 个记录的位置是空洞)。
另外,更新索引上的值,能够理解为删除一个旧的值,再插入一个新值。不难理解,这也是会形成空洞的。
所以,大量的增删改以后的表,都是可能存在很大的“数据空洞”的。
所以,咱们就能解释,为何分表后的总存储变大了。
由于分表后,须要从老库全量同步数据到新库,数据同步平台开启多个线程进行同步,插入各个分表并非按照递增的顺序插入的,所以,会产生巨量的“数据空洞”,形成存储空间变大。
若是可以把这些空洞去掉,就能达到收缩表空间的目的。而重建表就能达到这样的目的。
若是咱们手动重建一张表,能够新建一个与表 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 会自动完成转存数据、交换表名、删除旧表的操做。显然,花时间最多的步骤是往临时表插入数据的过程,若是在这个过程当中,有新的数据要写入到表 A 的话,就会形成数据丢失。所以,在整个 DDL 过程当中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。
MySQL 5.6 版本开始引入的 Online DDL,对这个操做流程作了优化。
能够看到,在这个过程当中,因为日志文件记录和重放操做这个功能的存在,这个方案在重建表的过程当中,容许对表 A 作增删改操做。这也就是 Online DDL 名字的来源。
须要补充说明的是,上述的这些重建方法都会扫描原表数据和构建临时文件。对于很大的表来讲,这个操做是很消耗 IO 和 CPU 资源的。所以,若是是线上服务,你要很当心地控制操做时间。
optimize table、analyze table 和 alter table 这三种方式重建表的区别:
参考内容:
丁奇 《MySQL 45讲》
看到这里了,原创不易,点个关注、点个赞吧,你最好看了~
知识碎片从新梳理,构建Java知识图谱:https://github.com/saigu/JavaKnowledgeGraph(历史文章查阅很是方便)
扫码关注个人公众号“阿丸笔记”,第一时间获取最新更新。同时能够免费获取海量Java技术栈电子书、各个大厂面试题。