没有主键会导致MySQL迁移失败,因工具依赖主键或唯一索引实现增量识别、行级比对和断点续传;无主键时无法判断行是否已同步或变更,引发重复数据、校验失效等问题。

为什么没有主键会让迁移失败
MySQL 数据迁移工具(比如 mysqldump、pt-online-schema-change 或自研同步脚本)默认依赖主键或唯一索引做增量识别和行级比对。没有主键的表,mysqldump --single-transaction 仍能导出快照,但后续增量同步、去重校验、断点续传几乎全部失效——因为无法确定某一行“是否已同步过”或“哪一行发生了变更”。
常见错误现象包括:
- 使用
pt-online-schema-change时报错:Cannot find a usable unique index on table `db`.`t_old` - 基于时间戳字段做增量时,发现同一秒插入多条记录,无法区分顺序
- 目标库出现重复数据,且无法用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE消除
用 ROW_NUMBER() + 临时主键补全逻辑
MySQL 8.0+ 支持窗口函数,可在导出前为每行生成稳定序号作为临时主键依据。关键不是真加主键,而是构造可复现的唯一标识。
操作步骤:
• 在源库执行导出前,先建一个带序号的视图或临时表:
CREATE TEMPORARY TABLE t_old_with_rn AS SELECT *, ROW_NUMBER() OVER (ORDER BY id, created_at, data_hash) AS _rn FROM t_old;
• 导出时按 _rn 分批次(例如每 10 万行一组),并记录最大 _rn 值作为断点:
SELECT * FROM t_old_with_rn WHERE _rn BETWEEN 1 AND 100000;
• 目标库导入后,用 _rn 字段辅助校验一致性(注意:该字段仅用于迁移过程,不保留到生产表结构)
• 若源库是 MySQL 5.7 或更早,改用 @rownum := @rownum + 1 变量模拟,但必须确保 ORDER BY 稳定(避免无索引排序导致每次结果不同)
用 MD5(CONCAT(...)) 构造内容指纹
当业务允许“按内容判重”,且字段组合能代表整行语义唯一性时,可用哈希值替代主键。
适用场景:
• 表中无任何索引,但业务上 user_id + event_type + event_time 组合不会重复
• 字段不多、长度可控(避免 MD5 输入超长导致性能下降)
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
实操建议:
- 在源库加临时列(仅迁移期用):
ALTER TABLE t_old ADD COLUMN _row_fingerprint CHAR(32) GENERATED ALWAYS AS (MD5(CONCAT(COALESCE(user_id,''), '-', COALESCE(event_type,''), '-', UNIX_TIMESTAMP(event_time)))) STORED;
- 导出时带上该列,目标库用它做
INSERT IGNORE或ON DUPLICATE KEY UPDATE的判断依据 - 务必对所有参与拼接的字段用
COALESCE处理 NULL,否则CONCAT返回 NULL,整个哈希失效
跳过主键依赖的迁移方式:文件直传 + LOAD DATA INFILE 如果只是单次迁移、不需实时同步,且源/目标库网络互通,最稳的方式是绕过 SQL 层协议,走文件直传。
要点:
• 用 SELECT ... INTO OUTFILE 生成制表符分隔文本(比 CSV 更少转义问题):
SELECT id, name, created_at FROM t_old INTO OUTFILE '/tmp/t_old.txt' FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n';
• 注意:secure_file_priv 必须允许该路径,且文件权限需被 MySQL 进程读取
• 目标库用 LOAD DATA INFILE 导入,速度比逐条 INSERT 快 10 倍以上;若目标库禁用 LOCAL,改用 mysqlimport 命令行工具
• 这种方式完全不依赖主键,但无法自动处理字符集转换、NULL 值表示(默认用 \N)、或字段类型隐式转换,需提前对齐源/目标表结构
没有主键的表迁移,真正难的不是“怎么导出来”,而是“怎么确认导得全、没重复、可验证”。只要在导出阶段就固化行顺序或内容标识,后面所有环节才有锚点。别指望靠目标库加个主键再反向推——旧数据的乱序和重复,往往在第一步就埋好了。










