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

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 爆增。










