CREATE TABLE ... AS SELECT 会丢失索引、主键、外键和 AUTO_INCREMENT 属性,导致查询性能骤降;应先用 SHOW CREATE TABLE 获取完整建表语句并手动重建索引,再灌数据后统一建索引,并显式指定字符集与排序规则以确保索引生效。

大表迁移时直接 CREATE TABLE ... AS SELECT 会丢索引
这个操作只复制数据和列定义,不复制主键、唯一约束、普通索引,甚至不复制 AUTO_INCREMENT 属性。迁移后表看起来“能用”,但查询一上量就慢——因为没索引,执行计划全走全表扫描。
实操建议:
- 先用
SHOW CREATE TABLE old_table拿到完整建表语句,手动提取索引定义(PRIMARY KEY、UNIQUE KEY、KEY) - 新建空表时把索引全部写进去,不要依赖
AS SELECT自动继承 - 如果原表有外键,
AS SELECT更不会带过去,后续ALTER TABLE ... ADD FOREIGN KEY还得校验数据一致性,容易卡住
先灌数据再建索引,比边插边索引快 3–10 倍
MySQL 在插入过程中维护 B+ 树索引开销极大:每条记录都要定位页、分裂页、更新父节点。而一次性建索引可走排序构建(sort_buffer + 批量插入),I/O 和 CPU 利用率都高得多。
实操建议:
- 迁移前关闭唯一性检查:
SET unique_checks = 0;建完索引再开回来 - 临时关掉自动提交:
SET autocommit = 0,等数据导入完成再COMMIT - 用
INSERT INTO new_table SELECT * FROM old_table灌数据,别用逐行INSERT - 建索引统一在最后做:
ALTER TABLE new_table ADD INDEX idx_col (col),多个索引可以一条ALTER里加完(5.6+ 支持)
pt-online-schema-change 不适合纯迁移场景
它本质是“在线改结构”,靠触发器同步增量变更,目标表必须和原表同库同名(或用 --new-table 指定),且全程锁原表元数据(FLUSH TABLES WITH READ LOCK 时间虽短但存在)。你只是想换个表名/库,没必要引入复杂同步逻辑和长事务风险。
实操建议:
- 如果是跨库迁移,用
mysqldump --no-create-info --skip-triggers导数据,再用mysql客户端导入,配合前面说的索引分步策略更可控 - 如果要停机窗口充足,直接
RENAME TABLE切换(先建好新表+索引,再原子重命名)比任何在线工具都稳 - 注意
pt-osc默认会删触发器,若中途失败,残留触发器可能污染原表写入
字符集和排序规则不一致会导致索引失效
比如原表是 utf8mb4_unicode_ci,新表建成了 utf8mb4_general_ci 或 utf8mb4_0900_as_cs,即使字段类型一样,WHERE col = 'x' 也可能无法走索引——因为比较逻辑变了,优化器不敢用。
实操建议:
- 建表时显式指定
CHARACTER SET和COLLATE,别依赖数据库默认值 - 用
SHOW FULL COLUMNS FROM old_table核对每个字段的Collation,确保新表字段级一致 - 迁移后跑
EXPLAIN FORMAT=TREE SELECT ...,确认关键查询真用了索引,而不是“Using filesort”或“Using temporary”
真正麻烦的不是数据量大,而是索引定义散落在各种地方:建表语句、ALTER 历史、运维脚本里。漏一个 KEY,线上查慢一倍,还很难定位。



















