数据迁移实战:从 MySQL/MongoDB 迁到 PostgreSQL

把生产库迁到 PostgreSQL 是常见但高风险的工程。本文系统讲解迁移评估(兼容性差异矩阵)、迁移方案(逻辑导出导入/ETL 工具/双写 CDC)、MySQL 到 PG 的类型与语法差异、MongoDB 到 PG 的文档关系映射、零停机切换流程(双写/校验/回滚)、以及迁移后的验证与优化清单。

引言

迁库是数据库工程里风险最高、最不可逆的操作之一——数据是资产,错一步就丢信任。无论从 MySQL(关系型换关系型)还是 MongoDB(文档型换关系型),迁移的本质都是:评估差异 → 制定方案 → 全量同步 → 增量追平 → 灰度切换 → 双写验证 → 稳定后收尾。

本文系统给出可执行的迁移路线图:先是兼容性差异矩阵(避免「迁完才发现语法不对」),再讲三种迁移方案与选型,然后分别拆解 MySQL→PG 与 MongoDB→PG 的映射要点,最后是零停机切换与回滚的完整剧本。

前置:/postgresql-vs-mysql-vs-mongodb/(选型对比)、/postgres-logical-replication/(CDC 与逻辑复制)。


目录


1. 迁移决策:值不值得迁、迁什么

迁移的正当理由:

□ 需要更丰富的类型/索引/扩展(PG 特性)
□ 多库并存想统一技术栈
□ MySQL 升级/授权成本高
□ 需要更强的 SQL 标准兼容与约束能力

不该迁的理由(先别动手):

□ 「PG 更高级」但业务没诉求
□ 团队不熟悉 PG,运维无人
□ 高并发写入场景未评估清楚

迁移前必须搞清:

□ 数据量、增长速率
□ 上下游依赖(应用、BI、同步管道)
□ 可接受的停机窗口(决定方案)
□ 业务高峰时段(定切换时机)

记忆:迁移是「工程」不是「搬运」——先算清差异与停机预算,再谈方案。


2. 兼容性差异矩阵:评估先行

建一张差异清单逐项核对,避免迁移期才发现:

维度MySQLPostgreSQL注意
字符串长度varchar 按字符text/varcharPG 不限长度,超长改 text
自增AUTO_INCREMENTIDENTITY/serial语法差异
布尔TINYINT(1)boolean类型映射
时间TIMESTAMP(时区麻烦)timestamptz建议统一 UTC
全文FULLTEXTtsvector + GIN语法完全不同
插入冲突INSERT ... ON DUPLICATE KEYON CONFLICT语义需核对
大小写表名可大小写混用一律小写(除非引号)迁移时统一小写
JSONJSON(文本存储)jsonb(二进制)映射后查询语法差异

评估方法:写一个「schema + 代表查询」的兼容性对照脚本,在新库跑一遍代表业务查询。


3. 三种迁移方案与选型

方案原理停机适用
逻辑导出导入mysqldump/pg_dump + 转换需停机小库、可停机
ETL 工具pgloader / 自研脚本短停机中大型、有结构转换
双写 + CDC应用双写 + Debezium零停机大型、生产在线

方案对比:

□ 停机迁移:简单、风险集中、适合可接受几分钟停机的小系统
□ pgloader:专为 MySQL→PG 设计,自动类型映射
□ 双写 CDC:零停机但工程量大(改应用 + 部署同步管道)

决策:先看停机预算——预算够用 pgloader/导出导入;预算为零必须双写 CDC。


4. MySQL→PostgreSQL:类型与语法映射

类型映射表(pgloader 自动做,手工迁移参考):

MySQLPostgreSQL
TINYINT/SMALLINTsmallint
INT/INTEGERinteger
BIGINTbigint
VARCHAR(n)varchar(n) / text
TEXTtext
DATETIME/TIMESTAMPtimestamptz
DATEdate
DECIMAL(p,s)numeric(p,s)
TINYINT(1)boolean
BLOBbytea
JSONjsonb

自增列迁移:

-- MySQL: id INT AUTO_INCREMENT PRIMARY KEY
-- PG:
CREATE TABLE users (
    id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    ...
);

pgloader 一行迁移:

pgloader mysql://user:pwd@host/mydb postgresql:///newdb

5. MySQL→PostgreSQL:SQL 差异清单

高频差异(应用 SQL 必须改的地方):

场景MySQLPostgreSQL
分页限制LIMIT 10 OFFSET 20相同(LIMIT/OFFSET)
插入冲突ON DUPLICATE KEY UPDATEON CONFLICT (id) DO UPDATE SET ...
字符串拼接CONCAT(a,b) / `a
逻辑或`
布尔WHERE active = 1WHERE active
时间函数NOW() 无时区now() 带时区
正则REGEXP~(POSIX)
GROUP BY宽松(可选非聚合列)严格(必须含聚合键)
反引号`col`双引号 "col"(或省略)
AUTO_INCREMENT 取回LAST_INSERT_ID()INSERT ... RETURNING id

RETURNING 是 PG 亮点:

INSERT INTO users(name) VALUES('x') RETURNING id;
-- 直接取回新行,省一次查询

注意:GROUP BY 严格性与布尔语义差异,是应用迁移最常踩的两处运行时坑。


6. MongoDB→PostgreSQL:文档到关系映射

MongoDB 文档模型 → PG 关系模型的映射策略:

Mongo 概念PG 映射说明
CollectionTable结构化的集合
内嵌文档jsonb 列 或 拆分表弱结构内嵌用 jsonb
数组字段text[]/jsonb 或关联表简单数组用数组列
动态字段jsonb保留扩展性
_id ObjectIduuid/bigint重映射主键
引用(手动关联)外键 + 正式关联规范化
嵌套子文档数组子表需要按子字段查询时

关键取舍:内嵌还是拆分?

□ 按内嵌字段查询/更新频繁 → 拆子表(可 JOIN、可约束)
□ 整块读写、无独立查询 → 保留 jsonb 列(省 JOIN、保留灵活性)
□ 混合 → jsonb 列 + 必要字段提升为正式列

示例映射:

// MongoDB 文档
{ "_id": ObjectId("..."), "user": "alice", "email": "a@x.com",
  "profile": {"age": 30, "city": "BJ"},
  "orders": [ {"id": 1, "total": 99}, {"id": 2, "total": 20} ] }
-- PG 映射:users + 内嵌 profile 用 jsonb + orders 拆子表
CREATE TABLE users (
    id uuid PRIMARY KEY,
    user text, email text,
    profile jsonb,          -- 弱结构保留
    ...
);
CREATE TABLE orders (
    id bigint PRIMARY KEY,
    user_id uuid REFERENCES users(id),
    total numeric(12,2)
);

7. 零停机切换流程

零停机剧本(双写 + CDC 场景):

Phase 1  评估与建模:差异矩阵、目标 schema 设计
Phase 2  全量同步:历史数据导入新库(快照时刻 T0)
Phase 3  CDC 增量追平:Debezium 把 T0 后的变更同步到 PG
Phase 4  双写验证:应用同时写两库,定期校验一致性
Phase 5  灰度切换:先切读流量 → 再切写流量(保留 MySQL 只读)
Phase 6  稳定收尾:MySQL 下线或转为只读备份

切换要点:

□ 全量快照时刻要记准,CDC 从该 LSN/binlog pos 开始
□ 灰度比例从 1% → 10% → 50% → 100%,逐步放量
□ 每次放量后校验关键报表与告警
□ 回滚预案:MySQL 侧一直保持可写到 Phase 6 前

8. 双写、校验与回滚

双写架构:

应用 → MySQL(权威,继续服务)
     → 同步管道(CDC: binlog→PG)
     → PostgreSQL(目标,异步追平)

一致性校验:周期对比两库关键表:

-- 以 MD5 指纹对比(示例:orders 表)
SELECT count(*), md5(string_agg(row_to_json(t)::text, '' ORDER BY id))
FROM orders t;   -- 在 MySQL 侧等价算指纹,两边比对

校验维度:

□ 行数一致
□ 关键字段抽样逐行比对
□ 金额/计数类聚合一致
□ 最新变更延迟(CDC lag)低于阈值

回滚预案:

□ 切换后 24-72h 内保留 MySQL 只读
□ 写流量切回只需改应用路由(连接串)
□ 数据回灌:切换期间新数据在 PG,需反向同步回 MySQL(备而不用)

记忆:双写 = 权威老库 + CDC 追平 + 指纹校验 + 可回滚路由——「切错了能退回来」是零停机方案的最后底气。


9. 迁移后的验证与优化

迁移完成 ≠ 结束,还有收尾三件事:

1. 数据验证:行数/抽样/聚合全量比对
2. 性能验证:压测代表查询,建齐索引
3. 能力收尾:重新 ANALYZE 统计、检查膨胀、建监控

迁移后必查清单:

-- 统计信息刷新(新库没有数据分布统计,查询计划会瞎)
ANALYZE;
-- 缺失索引发现:打开慢查询日志,跑一轮代表流量
-- 自增序列校准(identity 从正确起点继续)
SELECT setval(pg_get_serial_sequence('orders','id'),
              (SELECT max(id) FROM orders));

优化:利用 PG 优势补建缺失能力——JSONB 索引、GIN 全文、CONCURRENTLY 建索引。


10. 速查表与一句话记忆

阶段动作
评估差异矩阵 + 停机预算
方案pgloader / 导出导入 / 双写 CDC
全量快照时刻记准
增量Debezium 从 binlog/LSN 追平
切换读先写后,1%→100% 灰度
校验MD5 指纹 + 行数 + 延迟
回滚老库保留只读到稳定
收尾ANALYZE + 索引 + 序列校准

一句话记忆:迁移是「评估→全量→追平→灰度→收尾」的五步工程;停机预算决定方案,双写 CDC 换零停机;指纹校验 + 可回滚路由是最后底气——迁完还得 ANALYZE 补索引,才算出师。


延伸阅读

  • /postgresql-vs-mysql-vs-mongodb/ — 迁移前的选型对比
  • /postgres-logical-replication/ — CDC 与逻辑复制的底层
  • /postgres-backup-recovery/ — 迁移失败的安全网
  • /postgres-schema-design/ — 目标 schema 的设计规范
  • [[postgresql]] — PostgreSQL 数据库专题

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章

  1. 云托管与 Serverless PostgreSQL:RDS/Aurora/Neon/Supabase 选型与实战
  2. PostgreSQL 数据库设计规范:范式、类型选择与迁移演进
  3. PostgreSQL 备份与恢复深度实战:逻辑备份、物理备份、WAL 归档与 PITR