直接用 insert ... select distinct 会卡死,因其需构建临时表并全量排序或哈希去重,导致cpu和i/o飙升;应改用on duplicate key update、临时表预建唯一索引或insert ignore等索引驱动方案。

为什么直接用 INSERT ... SELECT DISTINCT 会卡死
面对百万级数据去重插入,很多人第一反应是 INSERT INTO t SELECT DISTINCT * FROM src。这在小表上没问题,但实际执行时你会发现:MySQL 会为整个 SELECT DISTINCT 结果集构建临时内存表(甚至落盘),同时对所有字段做全量排序或哈希去重——CPU 和磁盘 I/O 瞬间拉满,语句卡住十几分钟甚至 OOM。更糟的是,如果目标表有唯一索引,这种写法还无法利用索引加速判重。
用 INSERT ... ON DUPLICATE KEY UPDATE 替代全量 DISTINCT
前提是目标表已有合适的唯一约束(比如 UNIQUE KEY (a, b))。这时应让数据库在插入时「边插边判重」,而不是先去重再插入。MySQL 的 ON DUPLICATE KEY UPDATE 本质是利用索引快速定位冲突行,性能几乎与单条插入同量级。
- 确保源数据中不带重复主键/唯一键值,否则会触发大量
UPDATE(哪怕SET id=id也开销不小) - 避免在
ON DUPLICATE KEY UPDATE中写复杂表达式或子查询,它会在每条冲突行上执行,放大 CPU 开销 - 批量提交控制在 1000–5000 行/批,太小增加网络往返,太大易锁表;可用
LOAD DATA INFILE配合临时表预处理后再批量INSERT ... ON DUPLICATE KEY
大批量去重必须走临时表 + 唯一索引预过滤
当源数据本身含大量重复、且无法保证唯一性约束时,硬刚 DISTINCT 不现实。正确路径是:先载入临时表 → 加唯一索引 → 利用索引自动去重 → 再合并到目标表。
- 创建临时表用
CREATE TEMPORARY TABLE tmp AS SELECT ...,比INSERT INTO tmp SELECT ...更快(跳过日志和触发器) - 立刻在临时表上建
UNIQUE INDEX (col1, col2),MySQL 会自动丢弃重复行(报 warning,但不中断) - 用
INSERT IGNORE INTO target SELECT * FROM tmp完成最终写入;IGNORE比ON DUPLICATE KEY少一次 UPDATE 路径,更轻量 - 注意:该方案依赖 MySQL 的
sql_mode是否含STRICT_TRANS_TABLES,否则重复键警告可能被静默吞掉
真正卡顿的根源常在事务与锁机制
即使语法最优,百万级插入仍可能秒级变分钟级——问题往往不在 SQL 本身,而在事务隔离级别和锁行为。默认 REPEATABLE READ 下,InnoDB 会对扫描范围加间隙锁(gap lock),导致并发插入阻塞。
- 确认业务允许的前提下,把存储过程内事务设为
SET TRANSACTION ISOLATION LEVEL READ COMMITTED - 避免在循环里逐条
INSERT;务必批量提交,并在每批后显式COMMIT,防止长事务堆积 undo log - 检查目标表是否有未使用的二级索引,它们会在每次插入时全部更新,成为隐形瓶颈;可考虑先
DROP INDEX,插入完成再重建
去重速度的天花板不在算法,而在你是否让索引真正参与判重、是否绕开了全表扫描和长事务锁。临时表建索引那一步,才是百万级下真正能落地的“秒级”前提。










