暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL大量插入数据表空间膨胀问题

原创 月光族_DBA 2022-11-16
616

手下开发个单表迁移SHELL脚本,从A实例将数据迁移到B实例过程中,发现几乎稍大点的表在迁移完成后,目标端表空间大小差不多都是源端的3倍,也就是说表空间膨胀了2倍。

可能原因

  1. MySQL表默认是InnoDB引擎且目前索引只支持B+树索引,在数据的增删改过程中,会因为page分裂而导致表产生碎片,主从服务器上同张表的碎片率不同也会导致表空间相差很大。
  2. 主库整理过碎片(相当于重建整表),从库则是从原先的未整理的物理备份中恢复出来的。
  3. 两端表结构不一致,如从库可能比主库多索引。
  4. 两端表的行格式不一致,如主库为dynamic,从库为compressed。
  5. 两端字符集不同,例如源端是latin1,目标端是utf8mb4。
  6. 个别云数据库在从库上可能采用特殊的并行复制技术,导致在从库上有更高的碎片率(有个极端的案例,同一个表在主库只有6G,从库上则有将近150G)。
  7. 数据表上没有自增ID作为主键,数据写入随机离散,page频繁分裂造成碎片率很高。

问题发现

  1. 计算数据表总行数。
  2. 根据batch size,分成多段并行读取数据;例如总共10000行数据,batch size是1000,则总共分为10次读取数据。
  3. 将读取出来的数据拼接成 INSERT…VALUES…ON DUPLICATE KEY UPDATE,因为脚本要支持增量迁移数据,所以才加上 ON DUPLICATE KEY UPDATE 子句。
  4. 将拼接后的SQL并行写入到目标端。

初看上述工作过程,似乎也没什么特别之处会导致数据写入后产生大量碎片,从而表空间文件急剧膨胀。
首先,读取数据阶段只涉及到源端,可以先排除了。所以,疑点集中在第3、4两步。
了解InnoDB引擎特点的话应该知道,当InnoDB表有自增ID作为主键时,如果写入的数据总是顺序递增的话,那么产生碎片的概率就会很低。但是,如果写入的数据是离散化的

经过排查,终于发现问题所在,原来是脚本在拼接SQL时,虽然是分段读取数据,但没有将读取出来的结果集先行排序,造成了拼接后的SQL大概像下面这样的:

INSERT INTO t VALUES (100, ...), (99, ...), (98, ...)...(1, ...);

这种方式写入的话,而且还是并发写入,就会极大概率造成InnoDB data page频繁分裂,所以表空间文件才膨胀到原来的3倍之巨。原因不难理解,就好比排队机制,本来我们是按照身高顺序排,但现在有几位高个子的先排在前面了,那么后来的每次都要让这几个人频繁往后移动才行,这就造成了data page分裂,产生大量碎片。

关于数据页 分裂和重组可以参考下面两篇文章

InnoDB page directory split
InnoDB page directory slot balance

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论