直接大批量insert...select join会卡死,因优化器可能选错驱动表导致全表扫描+嵌套循环,或中间结果集撑爆tmp_table_size报error 1104;常见现象为show processlist卡在copying to tmp table或sorting result。

为什么直接 INSERT ... SELECT JOIN 会卡死
因为优化器可能对大表选错驱动顺序,导致全表扫描+嵌套循环;或者中间结果集撑爆 tmp_table_size,报错 ERROR 1104 (42000): The SELECT would examine more than MAX_JOIN_SIZE rows。常见现象是 SHOW PROCESSLIST 卡在 Copying to tmp table 或 Sorting result 状态,几分钟没响应。
CREATE TEMPORARY TABLE 必须带主键或索引
临时表建完不加索引,JOIN 时照样慢——默认无索引,优化器只能全表扫描。别用 CREATE TEMPORARY TABLE AS SELECT,它不支持 PRIMARY KEY 语法糖,字段类型还可能隐式变成 VARBINARY,后续 JOIN 失败。
- MySQL:用
CREATE TEMPORARY TABLE tmp_batch (id BIGINT PRIMARY KEY, value VARCHAR(100)) - PostgreSQL:先
CREATE TEMP TABLE tmp_batch AS SELECT ...,再CREATE INDEX ON tmp_batch(id) - SQL Server:
CREATE TABLE #tmp_batch (id BIGINT PRIMARY KEY),自动建聚集索引
分批 INSERT 要控制每次数据量
单次处理 5k–50k 行较稳妥;超过 10 万容易触发磁盘临时表。关键看关联字段有没有联合索引:(ref_id, status) 可放大到 10 万,只有单列 ref_id 索引就压到 2 万以内。
- 用主键范围分片,别用
LIMIT OFFSET:例如WHERE id BETWEEN 100001 AND 150000 - 每次插入前跑
EXPLAIN,确认type是ref或range,rows预估接近你设的批次大小 - INSERT 后立即
TRUNCATE TEMPORARY TABLE tmp_batch,再填充下一批,避免残留干扰
字段类型和字符集必须严格一致
如果目标表的 user_id 是 BIGINT,临时表对应字段却建成了 INT,MySQL 会做隐式转换,索引直接失效;同理,utf8mb4 和 latin1 混用也会让 JOIN 退化为全表扫描。
- 建表时显式指定类型:
user_id BIGINT UNSIGNED NOT NULL - 检查
SHOW CREATE TABLE target_table和临时表 DDL 是否完全匹配 - 字符串字段慎用
VARCHAR(2000):MEMORY 引擎不支持长字段索引,InnoDB 也要注意前缀索引长度
INSERT INTO target SELECT ... FROM tmp_batch JOIN ... 执行时是否走索引嵌套循环。最容易被忽略的是:建表语句里漏了 PRIMARY KEY,或者目标表关联字段根本没索引——这两处任一缺失,整个方案就退回原点。










