能用insert into select迁移说明已在同一mysql实例,最快最省资源;但需显式对齐字段、带库名前缀、确保权限、目标表预先存在、大表分批处理、避免主键冲突及注意时区与触发器不生效。

能用 INSERT INTO SELECT 迁移,说明你已经在同一 MySQL 实例里——这是最快、最省资源的方式;但稍不注意就会报错中断,或悄悄丢数据。
字段必须显式对齐,别信 SELECT *
很多人写完 INSERT INTO t2 SELECT * FROM t1 就跑,结果报错 Column count doesn't match value count。这不是语法错,是目标表字段数、顺序、类型和 SELECT 结果不一致。
- 永远显式写出目标列:
INSERT INTO t2 (id, name, created_at) SELECT id, username, register_time FROM t1 -
SELECT *在源表加列后会多出一列,目标表没对应字段就直接失败 - 目标列是
NOT NULL且无默认值?那SELECT对应列不能返回NULL,得用IFNULL(username, '')或COALESCE(age, 0)处理 - 目标是
ENUM('a','b'),但SELECT返回了'c'?不会报错,但插入空字符串——查SHOW WARNINGS才能看到
跨库迁移时,库名前缀和权限缺一不可
MySQL 原生支持跨库引用,但不是“自动打通”。INSERT INTO target_db.t SELECT * FROM source_db.t 这句语法没问题,错在权限或前缀漏写。
- 必须带库名前缀:漏掉
source_db.,SELECT就查当前默认库的表,不是你想迁的那个 - 执行用户需同时拥有:
SELECT权限在source_db.t,INSERT权限在target_db.t;少一个就报ERROR 1142 (42000): INSERT command denied或SELECT command denied - 目标表必须存在——
INSERT INTO SELECT不建表;想复制结构,先CREATE TABLE target_db.t LIKE source_db.t,再插数据
大表迁移卡住?不是慢,是锁和事务在拖后腿
百万级数据跑一条 INSERT INTO SELECT 卡死,通常不是 SQL 写得不好,而是 InnoDB 的锁机制和事务配置在起作用。
- 在
REPEATABLE READ隔离级别下,SELECT部分会对扫描到的索引范围加 next-key lock,若没走索引,等于锁全表区间,线上写入会被阻塞 - 别依赖
LIMIT分批:MySQL 5.7 及更早版本不支持子查询里用LIMIT,会报This version of MySQL doesn’t yet support ‘LIMIT & IN/ALL/ANY/SOME subquery’ - 正确分批方式:用主键范围切分,例如
WHERE id BETWEEN 1000001 AND 1010000;配合SET autocommit = 0+ 手动COMMIT控制事务大小 - 避开高峰期;如果必须在线跑,优先考虑
pt-archiver或应用层流式拉取,而不是硬扛一条大语句
重复数据导致整条语句回滚?ON DUPLICATE KEY UPDATE 是解药
INSERT INTO SELECT 是原子操作:只要一行违反主键或唯一约束,整个语句回滚。百万数据里混一条脏数据,前面都白跑。
- 先检查冲突:
SELECT COUNT(*) FROM source_db.t WHERE id IN (SELECT id FROM target_db.t) -
INSERT IGNORE INTO ... SELECT可跳过冲突行,但 MySQL 8.0.19+ 对IGNORE语义有调整,部分警告不再抑制 - 更可控的是
INSERT INTO target_db.t (...) SELECT ... ON DUPLICATE KEY UPDATE id = id(空更新),它只跳过冲突行,其余照插 - 注意:
ON DUPLICATE KEY UPDATE在 MySQL 8.0.19 之前不支持跨库目标表,升级前务必确认版本
最容易被忽略的点:时区和触发器。源字段是 TIMESTAMP、目标是 DATETIME,会话时区和系统时区不一致时,时间会偏移 1~2 小时;目标表有触发器?INSERT INTO SELECT 不触发它们——这行为是设计如此,不是 bug,但常被当成遗漏逻辑。











