脱敏迁移需先导出纯结构:用 mysqldump --no-data --skip-triggers --routines=false --events=false 导出 ddl;再行级脱敏数据,避免 shell 替换;导入前禁用外键与唯一检查;时间字段统一偏移,禁用 now() 默认值。

mysqldump 导出时必须加 --no-data 和 --skip-triggers
结构导入不等于全量导出再删数据,直接用 mysqldump 默认行为会把生产数据也拖进来,哪怕你后面手动删表——触发器、事件、存储过程可能悄悄执行或报错。脱敏迁移的第一步是「只拿结构」。
实操建议:
- 用
mysqldump --no-data --skip-triggers --routines=false --events=false -h prod-host -u user -p db_name > schema.sql导出纯 DDL -
--skip-triggers很关键:生产库的触发器常含敏感逻辑(比如自动写审计日志),开发环境没对应表或权限,一恢复就报ERROR 1442 (HY000): Can't update table in stored function/trigger -
--routines=false避免导出含敏感查询逻辑的函数/存储过程;真要保留,得人工 review 每个CREATE FUNCTION里有没有SELECT ... FROM user_profile这类语句
INSERT 语句里的手机号、身份证号不能靠正则替换就完事
脱敏不是“把 138****1234 替成 138****0000”——很多业务字段是加密后存的(比如 AES 加密的手机号),或者和其它字段有强关联(如 user_id 和 open_id 要保持映射一致),盲目替换会导致外键失效、联合索引查不出数据。
实操建议:
- 优先用 MySQL 自带的
REPLACE()或CONCAT()在导出前做行级脱敏,例如:SELECT id, CONCAT('u_', id) AS user_id, CONCAT('138', LPAD(id%10000, 4, '0')) AS phone FROM users - 避免用 shell 的
sed处理大 SQL 文件:它不识别 SQL 语法边界,可能把注释里的字符串或字段名也替了 - 如果表有 JSON 字段(如
extra_info),必须用JSON_REPLACE()或JSON_SET()更新内部键值,不能当普通文本替换
开发库导入前要关掉 FOREIGN_KEY_CHECKS 和 UNIQUE_CHECKS
结构 + 脱敏数据一起导入时,外键约束和唯一索引会卡在中间某条 INSERT 上,尤其是你用脚本批量生成测试数据时,ID 可能重复、父表还没插子表先插了——错误信息通常是 ERROR 1452 (23000): Cannot add or update a child row 或 ERROR 1062 (23000): Duplicate entry。
实操建议:
- 在
schema.sql开头加上:SET FOREIGN_KEY_CHECKS = 0; SET UNIQUE_CHECKS = 0;;结尾补上SET FOREIGN_KEY_CHECKS = 1; SET UNIQUE_CHECKS = 1; - 别依赖客户端自动设置:MySQL Workbench、DBeaver 默认不关这些,命令行
mysql -e "source schema.sql"也不自动关 - 导入完成后立刻验证:运行
SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE WHERE CONSTRAINT_SCHEMA = 'dev_db' AND CONSTRAINT_NAME LIKE 'fk_%';确认外键实际建上了,而不是被跳过
时间字段(created_at、updated_at)别用 NOW(),用固定偏移量
开发环境看到一堆「2025-03-12 14:22:33」这种未来时间戳,会让排查逻辑 bug 时误判时间顺序,尤其涉及定时任务、缓存过期、流水号生成等场景。
实操建议:
- 脱敏导出数据时,把时间字段统一减去一个固定天数,比如所有
created_at都变成DATE_SUB(created_at, INTERVAL 365 DAY),让时间落在近一年合理范围内 - 禁止在 DDL 里给时间字段设默认值为
NOW()或CURRENT_TIMESTAMP:开发库重启、从备份恢复时会重置成当前时间,破坏数据时序一致性 - 如果业务代码依赖「创建时间早于更新时间」这类断言,导入后跑一次
UPDATE orders SET updated_at = created_at + INTERVAL 1 SECOND WHERE updated_at
脱敏迁移最耗时间的环节从来不是导出或导入,而是确认每张表的主外键关系、每个字段的业务含义是否还在——比如 status 是枚举值还是状态机流转码,改错了整条订单流程就卡住。留半小时逐表核对 SHOW CREATE TABLE 输出,比重跑三遍迁移脚本更省事。











