INSERT INTO ... SELECT 是 MySQL 中开销最小、最可控的表间数据迁移方式,全程服务端执行,但需严格匹配字段顺序与类型;CREATE TABLE ... AS SELECT 适合一次性建表快照,但不复制约束和索引。

INSERT INTO ... SELECT 是最直接的方式
想把一张表的查询结果快速写入新表,INSERT INTO ... SELECT 是 MySQL 原生支持、开销最小、也最可控的方案。它不经过客户端中转,全程在服务端完成,避免了网络传输和应用层序列化/反序列化开销。
常见错误是漏掉目标表字段定义或类型不匹配——MySQL 不会自动转换字段名或类型,SELECT 的列顺序和数量必须与 INSERT INTO 后的字段列表(或目标表结构)严格对应。
- 如果目标表不存在,先用
CREATE TABLE ... AS SELECT创建并填充(但注意:该语句不会复制索引、主键、外键等约束) - 如果目标表已存在且有主键/唯一约束,需确认
SELECT结果不产生重复或 NULL 违例,否则语句会失败 - 大表操作建议加
WHERE条件分批执行,避免长事务阻塞或日志暴涨;必要时在会话级关闭 autocommit 并手动控制事务
CREATE TABLE ... AS SELECT 适合“一次性快照”场景
当你要基于查询结果生成一个全新表(比如导出中间分析表、备份某时刻状态),CREATE TABLE ... AS SELECT 比先 CREATE TABLE 再 INSERT 更简洁。但它只复制数据和列定义(含类型、是否允许 NULL),不复制任何约束、索引、触发器或默认值表达式。
典型误用是以为它能继承原表的主键:CREATE TABLE t2 AS SELECT * FROM t1 得到的 t2 没有主键,哪怕 t1 有。
- 若需保留主键或索引,必须后续单独执行
ALTER TABLE t2 ADD PRIMARY KEY (...)或ADD INDEX - 该语句默认使用
MyISAM引擎(旧版本),新版本 MySQL 默认为InnoDB,但仍建议显式指定:CREATE TABLE t2 ENGINE=InnoDB AS SELECT ... - 不支持在
AS SELECT中引用刚创建的表名(即不能自嵌套)
批量插入性能关键在批量大小和事务控制
即使走 INSERT INTO ... SELECT,如果源表极大(千万行以上),单条语句仍可能因 undo 日志、binlog 写入或锁竞争变慢。真正影响速度的不是语法本身,而是事务粒度和服务器配置。
例如,对一个 5000 万行的表做全量迁移,直接 INSERT INTO t2 SELECT * FROM t1 可能卡住几小时;而拆成每 10 万行一批、带明确 WHERE id BETWEEN ? AND ? 条件的循环插入,反而更稳更快。
- 确保
innodb_log_file_size足够大(至少 1GB),避免频繁刷盘 - 临时关闭唯一性检查(仅限可信数据):
SET unique_checks=0,导入完再开 - 大批量插入前可考虑关闭 autocommit:
SET autocommit=0,最后COMMIT,但要注意内存和锁持有时间 - 避免在
SELECT中用复杂子查询或函数,它们会在每一行上重复计算,拖慢整体速度
别忽略字符集和时区隐式转换风险
两张表字符集不同(比如源表是 utf8mb4,目标表是 latin1),或字段类型隐式转换(如 SELECT 返回 VARCHAR(255),目标列为 CHAR(10)),MySQL 会静默截断或报错,取决于 SQL mode 设置。
典型报错如:Data too long for column 'name' at row 1 或更隐蔽的乱码问题,尤其出现在中文、emoji 场景下。
- 执行前用
SHOW CREATE TABLE对比源表和目标表的字段定义、字符集、排序规则 - 显式 cast 类型:
CAST(col AS CHAR(50))或CONVERT(col USING utf8mb4)避免歧义 - 检查会话变量:
SELECT @@character_set_client, @@collation_connection,确保与表一致
实际迁移时,最耗时的环节往往不在语法选择,而在没提前校验字段兼容性和事务资源分配。特别是跨环境(如从测试库导到生产库)时,字符集、SQL mode、甚至 time_zone 都可能让同一句 INSERT INTO ... SELECT 行为完全不同。


















