当前位置: 首页 > 科技观察

MySQL批量导入数据时,为什么表空间扩大了N倍

时间:2023-03-17 09:58:45 科技观察

?问题是同事在客户现场使用DTS工具将数据从A实例迁移到B实例引起的。目标端的表空间大小几乎是源端的三倍,也就是说表空间扩大了两倍。如果你对本文《叶问》第16篇有印象,你应该还记得在数据迁移(导入导出)过程中,包括主从复制场景,表空间扩容的原因有几种:MySQLtabledefault是InnoDB引擎,目前索引只支持B+树索引。在增删改查数据的过程中,表会因为分页而产生碎片。同一个表在主从服务器上的碎片率差异也会导致表空间差异较大。主库进行了碎片整理(相当于重建了整个表),从库从原来的无组织物理备份中恢复。两端的表结构不一致,比如从库的索引可能比主库多。两端表的行格式不一致,比如主库是动态的,从库是压缩的。两端的字符集不同,比如源端是latin1,目标端是utf8mb4。一些云数据库可能在从库上使用了特殊的并行复制技术,导致从库上的碎片率较高(极端情况下,同一张表在主库只有6G,从库有将近150G)。数据表没有以自增ID为主键,数据随机离散写入,分页频繁导致碎片率高。问题发现按照上面的思路,一一排查,看能否定位到问题的原因。因素1不存在。这是一个全量迁移的场景,在日常的随机增删改查过程中不会造成通胀。因素2不存在。这是使用DTS工具迁移数据的场景。因素3、4、5不存在,两边表结构相同。因子6不存在,原因同2。因子7,不存在,每张表都有一个自增ID作为主键。查到这里,好像有点奇怪,好像遇到了玄学问题。不过没关系,我们还需要了解DTS工具的工作方式,大致如下:计算数据表的总行数。根据batchsize,将数据分成多段并行读取;比如总共有10000行数据,batchsize为1000,那么一共读入10次数据。将读取的数据拼接成INSERT...VALUES...ONDUPLICATEKEYUPDATE,因为DTS工具支持增量数据迁移,所以增加了ONDUPLICATEKEYUPDATE子句。将拼接好的SQL并行写入目标端。乍一看上面的工作过程,好像没什么特别的,就是数据写入后会产生大量的碎片,从而使表空间文件迅速膨胀。首先,读取数据阶段只涉及源端,可以先排除。所以,疑惑集中在第3步和第4步。如果了解InnoDB引擎的特性,应该知道,当InnoDB表以自增ID为主键时,如果写入的数据总是顺序递增,碎片的概率会很低。但是,如果写入的数据是离散化的(比如插入的顺序是随机的,或者比如插入的顺序是1、10000、2、3000、3、5000……完全离散无序),那么就有是大概率造成高分片率。根据以上疑惑,我们需要确认一下DTS工具构造的是一个什么样的SQL。这需要修改选项binlog_format=语句。这是获取其原生SQL。在行模式下排除故障可能相对困难。然后再次运行DTS工具,查看生成的SQL。经过排查,终于找到了问题所在。原来是DTS工具在拼接SQL时分段读取数据时,没有先对读取的结果集进行排序,导致拼接后的SQL大致如下:INSERTINTOtVALUES(100,...),(99,...),(98,...)...(1,...);如果这样写的话,还是并发写,很大概率InnoDB的数据页会被频繁的split,所以表空间文件会膨胀到原来的三倍大。道理不难理解,就像排队机制一样。本来我们是按身高顺序排列的,现在前面有几个个子高的,后面每次都得让这些人频繁的后退。这导致数据页分裂,产生大量碎片。我用了上万张sysbench标准表进行测试,这样写大概会造成20%左右的表空间膨胀率。问题已经很清楚了。只需要在读取数据插入SQL阶段先对结果集进行排序,就可以完美解决这个问题。并顺手向负责SQL优化器的同学提交了一个特性请求(MySQLbug#109087),希望能自动完成上述逆向INSERT情况下的SQL重写,将逆序改为正序(或者INSERT的顺序顺序与表主键的定义一致,通常是INT的正序),就可以完美避免这种风险。