INSERT INTO ... SELECT 是多表合并首选写法,需显式指定字段、用 UNION ALL 提升性能,目标表不存在时优先用 CREATE TABLE ... SELECT,大表合并须分批并校验字段类型。

INSERT INTO ... SELECT 多表合并最常用写法
直接用 INSERT INTO target_table SELECT ... FROM source_table 是合并多张结构兼容表的首选。它不依赖临时表,不触发大量日志(取决于引擎和模式),执行快、语义清晰。
常见错误现象:Column count doesn't match value count —— 源表字段数与目标表列数/顺序不一致;或用了 SELECT * 但各表字段顺序不同。
- 必须显式列出字段名,不要用
SELECT *,例如:INSERT INTO user_all (id, name, created_at) SELECT id, name, created_at FROM user_2023 UNION ALL SELECT id, name, created_at FROM user_2024 - 所有
SELECT子句字段数量、类型、顺序必须严格一致;类型不兼容时 MySQL 可能静默转换(如VARCHAR插入INT字段会截断或报错,取决于 SQL mode) - 若目标表有自增主键,且你想保留源数据的原始
id,需先执行SET FOREIGN_KEY_CHECKS = 0; SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';,并确保目标表允许显式插入自增值(ALTER TABLE user_all MODIFY id INT NOT NULL;若原为INT AUTO_INCREMENT)
UNION ALL 还是 UNION?性能差在哪
UNION ALL 比 UNION 快得多,因为不查重、不排序。99% 的表合并场景应该用 UNION ALL。
使用场景:你只是把几张历史分表(如按月拆分的 order_202401、order_202402)堆叠进一张宽表,不做去重处理。
-
UNION会隐式添加DISTINCT+ 排序,对大表可能触发磁盘临时表,IO 和 CPU 开销陡增 - 如果真需要去重,建议先用
UNION ALL写入临时表,再用CREATE TABLE deduped AS SELECT DISTINCT ... FROM temp_table单独处理,更可控 - 注意:MySQL 8.0+ 支持 CTE,但
WITH t AS (...) INSERT INTO ... SELECT * FROM t仍受限于单条语句不能嵌套多个 DML,所以还是得靠UNION ALL直连
目标表不存在?用 CREATE TABLE ... SELECT 更安全
如果目标表还没建,别先 CREATE TABLE 再 INSERT,直接用 CREATE TABLE new_table AS SELECT ... 一步到位,字段类型和 null 属性自动推导。
容易踩的坑:CREATE TABLE ... SELECT 不会复制原表的索引、主键、注释、默认值、外键约束——这些全得手动补。
- 执行后立刻检查:
SHOW CREATE TABLE new_table,确认PRIMARY KEY、INDEX是否缺失 - 字符集和排序规则继承自第一个
SELECT子句的源表,若各表字符集不一致(如一个用utf8mb4_0900_as_cs,一个用utf8mb4_general_ci),会报错Illegal mix of collations - 想保留注释?只能先建表:
CREATE TABLE new_table (...) COMMENT '合并后的订单表';,再INSERT ... SELECT
大表合并卡住或 OOM?控制批量和连接参数
单条 INSERT INTO ... SELECT 合并几十 GB 表,容易锁表、打爆 buffer pool、触发 long query kill,甚至让从库复制延迟爆炸。
性能影响核心在:InnoDB 的 undo log 增长、redo log 刷盘压力、以及是否启用了 binlog_row_image=FULL(默认)导致 binlog 膨胀。
- 拆成小批次:用
WHERE id BETWEEN ? AND ?分页,配合ORDER BY id避免重复,每次插 10 万行左右 - 调低事务粒度:在客户端循环中每批后显式
COMMIT,避免长事务拖慢 MVCC 清理 - 关键配置检查:
innodb_log_file_size至少是最大单批数据量的 2 倍;sort_buffer_size和read_rnd_buffer_size不宜设过高(512K~2M 足够),否则多个并发合并会挤占内存
实际合并时,最常被忽略的是字段类型的隐式转换边界——比如源表 price 是 DECIMAL(10,2),目标表建成了 DECIMAL(8,2),MySQL 不报错但会截断,数据就悄悄错了。动手前务必 DESCRIBE 对比所有字段。











