读一行,拼一条 INSERT,执行,再读下一行。这样的导入脚本十来分钟就能写完,拿几万行测试数据跑也挺顺。数据量换成几十亿行以后,时间多半耗在每条语句背后数据库要做的那些事上。MySQL 8.4 手册有一节专讲 InnoDB 的批量导入(10.5.5 Bulk Data Loading for InnoDB Tables),下面按风险从小到大把里面的建议过一遍。
基本没副作用的几项
- 导入时关掉 autocommit,整批做完再 COMMIT。手册给的理由是 autocommit 模式下每插一行都要把日志刷一次盘。用 mysqldump 的
--opt选项导出的文件,手册说不用再套一层事务也能较快导进 InnoDB 表。 - 用多行 INSERT,一条语句带多组 VALUES,减少客户端和服务端之间的通信开销。这条对任何表都适用,不限 InnoDB。
- 按主键顺序插入。InnoDB 表用的是聚簇索引,按 PRIMARY KEY 顺序写入相对快,表大到 buffer pool 装不下时这一点尤其要紧。
- 源数据本来就是文本文件的,用 LOAD DATA。10.2.5.1 Optimizing INSERT Statements 里说,这通常比用 INSERT 语句快 20 倍。
LOAD DATA 也有几处要留意,都写在 LOAD DATA 语句页里:
- 不带 LOCAL 时文件放在服务器上,需要 FILE 权限,还受
secure_file_priv限制。带 LOCAL 时文件在客户端,服务端和客户端两边都得允许,mysqld 关着local_infile就会报错。 - 没写 REPLACE 的话,LOCAL 的效果等同于 IGNORE:唯一键重复的行直接丢弃,数据解析错误降成警告,导入照常继续。手册的解释是服务端没法中途叫停文件传输。导完用 SHOW WARNINGS 看一眼比较放心。
- 用 LOCAL 时服务端会在临时文件目录里复制一份文件,那里空间不够,语句就会失败。
- LOAD DATA 对基于语句的复制不安全。
binlog_format=STATEMENT时每个副本都会生成一个临时数据文件,源端开了 binlog 加密,这个文件也照样不加密。
要自己担责任的开关
- 二级索引上有 UNIQUE 约束时,可以在导入会话里临时把
unique_checks设成 0。大表能借 change buffer 批量写二级索引记录,省下不少磁盘 I/O。手册紧跟着的一句是:确保数据里没有重复键。检查关掉以后,重复键要靠导入前的数据清洗来挡。 - 有外键约束的表,导入期间把
foreign_key_checks设成 0,同样能省磁盘 I/O。这一节没写违反外键的数据进去以后会怎样,导之前自己核一遍父表数据更稳妥。 - 批量导入页仍建议把
innodb_autoinc_lock_mode从 1(consecutive)改成 2(interleaved)。8.4 的 AUTO_INCREMENT 处理页却写明 2 已经是默认值,而且这个变量要在启动时配置。代价也写在那页:用基于语句的复制,或者靠回放 binlog 里的 SQL 做恢复时,模式 2 不安全。有并发插入时,批量插入分到的自增值可能有空洞。用基于语句复制的库,手册要求设成 0(traditional)或 1(consecutive),源库和副本保持同一个值。 - 有全文索引的表,建表时就定义
FTS_DOC_ID列(BIGINT UNSIGNED NOT NULL),配一个叫FTS_DOC_ID_INDEX的唯一索引,先导数据,最后再建 FULLTEXT 索引。这一列每次 INSERT 或 UPDATE 都得单调递增。不预先建的话,InnoDB 会在 CREATE FULLTEXT INDEX 时把FTS_DOC_ID加成隐藏列,代价是重建整张表。
redo log 那个开关
批量导入页的倒数第二条,是往新实例导数据时考虑用 ALTER INSTANCE DISABLE INNODB REDO_LOG 关掉 redo 日志。Redo Log 一节补充说,关掉以后省下的是 redo 写入和 doublewrite 缓冲。
两页用的是同一段警告:这个功能只用于往新的 MySQL 实例导数据,不要在生产系统上关 redo 日志。关闭期间可以正常关机重启,但服务意外停掉的话,可能丢数据、损坏实例。真碰上了,重启会被拒绝并报错,手册说这时必须初始化一个新实例,从头再导一遍。
启用和关闭 redo 日志需要 INNODB_REDO_LOG_ENABLE 权限,当前状态可以查 Innodb_redo_log_enabled。关闭期间不能做克隆,也不能开 redo log 归档。
手册最后一条建议是用 MySQL Shell:util.importTable() 是并行的表导入工具,面向大数据文件。util.loadDump() 加载 dump 时也能并行。
这些手段各能省多少时间,除了 LOAD DATA 那一句,手册都没给数字,想知道效果得拿自己的数据分别试。有人在新实例上关过 redo log 做初始化导入吗?开回来以后有没有遇到别的问题?