mysql优化INSERT语句的索引使用与数据插入

7次阅读

insert 时索引拖慢速度是因为每行插入需更新所有非唯一二级索引,5个索引则实际写6次,批量插入线性放大开销;unique索引额外触发重复校验,fulltext索引需分词重建,开销更大。

mysql优化INSERT语句的索引使用与数据插入

INSERT 时索引为什么反而拖慢速度

mysql 在执行 INSERT 时,每插入一行,所有非唯一二级索引(包括 UNIQUE 和普通 INDEX)都需要更新 B+ 树结构。如果表有 5 个索引,单行插入实际要写 6 次(1 次主键聚簇索引 + 5 次二级索引)。批量插入时,这个开销会线性放大。

常见错误现象:INSERT INTO t VALUES (...),(...),(...) 批量语句耗时远超预期;或者用循环单条 INSERT 插入 10 万行,花了十几分钟。

  • 唯一索引(UNIQUE)还会触发重复值校验,每次都要走索引查找,比普通索引更重
  • FULLTEXT 索引在 INSERT 时会触发分词和倒排索引重建,开销极大
  • 如果 INSERT ... select 涉及大表扫描,还可能因索引统计不及时导致执行计划误判

批量 INSERT 前要不要 DROP INDEX

不是“要不要”,而是“什么时候值得”。临时删索引只对一次性导入海量历史数据(如 etl 场景)有效,且仅适用于非唯一索引。主键和 UNIQUE 索引不能删,否则违反约束。

实操建议:

  • 确认目标表当前无业务写入(或在维护窗口),否则 DROP INDEX 会导致 DDL 锁表,阻塞查询
  • 先用 SHOW INDEX FROM t 查出所有非唯一索引名,逐个 DROP INDEX idx_name ON t
  • 插入完成后再重建:用 CREATE INDEX idx_name ON t(col),不要依赖 ALTER table ... ADD INDEX,后者在 MySQL 8.0 前会重建整表
  • 注意:MySQL 8.0+ 支持 INVISIBLE 索引,可考虑设为不可见代替删除,避免 DDL 开销

INSERT DELAYED 已被移除,替代方案是什么

INSERT DELAYED 在 MySQL 5.6 被标记废弃,5.7 完全移除,不能再用。它原本是把插入请求放进内存队列异步写入,但存在数据丢失风险(服务崩溃即丢)、无法返回自增 ID、不支持事务等硬伤。

可靠替代方式:

  • 用批量 INSERT:一次最多 1000 行(受 max_allowed_packet 限制),例如
    INSERT INTO t (a,b) VALUES (1,2),(3,4),(5,6);
  • 开启 autocommit=0,手动控制事务边界:多个 INSERT 包在一个 START TRANSACTION / COMMIT 中,减少 redo log 刷盘次数
  • 调大 innodb_buffer_pool_size(建议设为物理内存 50%~75%),让索引页和数据页尽量缓存在内存中
  • 对日志类表等允许少量丢失的场景,可改用 MyISAM 引擎(但失去事务和行锁,慎选)

LOAD DATA INFILE 比 INSERT 快多少,要注意什么

在关闭索引、禁用唯一检查的前提下,LOAD DATA INFILE 通常比等量 INSERT 快 5~20 倍,因为它绕过 SQL 解析层,直接解析文本并批量构建记录页。

关键注意事项:

  • 必须确保文件在数据库服务器本地(除非启用 local_infile=ON 并用 LOAD DATA LOCAL INFILE,但多数云厂商默认禁用)
  • 执行前务必关掉唯一性检查:SET unique_checks=0,插入完再开:SET unique_checks=1(此时会重建唯一索引)
  • FIELDS TERMINATED BY ',' ENCLOSED BY '"' 显式声明格式,避免字段含逗号或换行导致错位
  • 如果源数据含中文,注意 CHARACTER SET utf8mb4 要与表一致,否则出现乱码或截断

真正容易被忽略的是:索引重建阶段(unique_checks=1 后)可能比导入本身还慢,尤其当唯一索引字段选择性差(如大量重复值)时,B+ 树分裂频繁,I/O 爆增。

text=ZqhQzanResources