大批量INSERT卡死或超时主因是索引维护、事务日志刷盘、锁竞争等底层开销激增;应禁用autocommit、unique_checks和外键检查,改用LOAD DATA INFILE,并慎用分区表。

大批量 INSERT 为什么卡死或超时
MySQL 在单表超过千万行后,INSERT 明显变慢,不是因为磁盘不够快,而是因为索引维护、事务日志刷盘、锁竞争这些底层开销指数级上升。尤其当 autocommit=1 且逐条插入时,每条都触发一次 redo log 写入和 binlog 刷盘,I/O 成瓶颈。
实操建议:
- 关闭自动提交:
SET autocommit = 0,把 1000–5000 行包进一个事务再COMMIT - 禁用唯一性检查(仅限导入可信数据):
SET unique_checks = 0,导入后再开 - 临时关闭外键约束:
SET foreign_key_checks = 0,避免每次 INSERT 都校验父表 - 用
LOAD DATA INFILE替代INSERT ... VALUES,快 5–10 倍,但要求文件在 MySQL 服务端可读
分区表(PARTITION)能解决插入慢吗
分区表对「插入性能」提升非常有限,甚至可能更慢——它本质是把一张表逻辑拆成多个子表(如按 created_at 每月一个 PARTITION),但写入仍走统一入口,InnoDB 层面的 buffer pool、锁、redo log 并不隔离。
真正起作用的场景只有两个:
- 按时间范围快速删旧数据:
ALTER TABLE t DROP PARTITION p202301是秒级,比DELETE FROM t WHERE ...快得多 - 查询能命中单个分区(如
WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31'),避免全表扫描
注意:PRIMARY KEY 或 UNIQUE KEY 必须包含分区字段,否则建表失败;MySQL 8.0 后已移除 HASH 分区对非整型字段的支持,TO_DAYS(created_at) 这类表达式必须确保返回整数。
分库分表后 INSERT 怎么路由不丢数据
分库分表本身不提供路由能力,得靠中间件(如 ShardingSphere、MyCat)或应用层逻辑。最常见错误是:用 user_id % 4 分 4 库,但没把 user_id 当做分片键传给中间件,结果数据乱插到不同库,主键冲突或查不到。
关键控制点:
- 所有涉及分片键的
INSERT必须显式带上该字段,不能依赖DEFAULT或触发器生成 - 批量插入时,同一语句里的多行必须落在同一个分片上,否则中间件会拒绝或拆成多条单行语句——性能归零
- 自增主键在分库后失效,必须用雪花算法、UUID 或号段模式生成全局唯一 ID,不能依赖
AUTO_INCREMENT - 跨分片
JOIN和GROUP BY几乎无法优化,这类查询要提前在应用层聚合或冗余字段
什么时候该放弃分区/分表,直接换存储引擎
当单表持续写入 > 5000 QPS,或单日新增 > 500 万行,且业务允许弱一致性时,MySQL 就不是最优解了。分区和分表只是“把问题分散”,但没减少单点压力——比如所有分片共用同一个 binlog dump 线程,主从延迟照样爆。
更现实的选择:
- 写多读少场景:切到
ClickHouse(列存 + 向量化执行),INSERT支持异步批量提交,吞吐轻松破 10w+/s - 需要强一致+高并发写:用
TiDB,分布式事务透明,自动分片,兼容 MySQL 协议,但运维复杂度明显上升 - 纯日志类数据:直接写
Kafka+ 定时落盘到对象存储(S3/OSS),用 Presto/Trino 查询,MySQL 只留热数据视图
最容易被忽略的一点:分库分表后,SELECT COUNT(*)、MAX(id) 这类聚合操作没法下推,应用层必须自己合并结果——不是加个中间件就万事大吉。


















