引言
迁库是数据库工程里风险最高、最不可逆的操作之一——数据是资产,错一步就丢信任。无论从 MySQL(关系型换关系型)还是 MongoDB(文档型换关系型),迁移的本质都是:评估差异 → 制定方案 → 全量同步 → 增量追平 → 灰度切换 → 双写验证 → 稳定后收尾。
本文系统给出可执行的迁移路线图:先是兼容性差异矩阵(避免「迁完才发现语法不对」),再讲三种迁移方案与选型,然后分别拆解 MySQL→PG 与 MongoDB→PG 的映射要点,最后是零停机切换与回滚的完整剧本。
前置:/postgresql-vs-mysql-vs-mongodb/(选型对比)、/postgres-logical-replication/(CDC 与逻辑复制)。
目录
- 1. 迁移决策:值不值得迁、迁什么
- 2. 兼容性差异矩阵:评估先行
- 3. 三种迁移方案与选型
- 4. MySQL→PostgreSQL:类型与语法映射
- 5. MySQL→PostgreSQL:SQL 差异清单
- 6. MongoDB→PostgreSQL:文档到关系映射
- 7. 零停机切换流程
- 8. 双写、校验与回滚
- 9. 迁移后的验证与优化
- 10. 速查表与一句话记忆
- 延伸阅读
1. 迁移决策:值不值得迁、迁什么
迁移的正当理由:
□ 需要更丰富的类型/索引/扩展(PG 特性)
□ 多库并存想统一技术栈
□ MySQL 升级/授权成本高
□ 需要更强的 SQL 标准兼容与约束能力
不该迁的理由(先别动手):
□ 「PG 更高级」但业务没诉求
□ 团队不熟悉 PG,运维无人
□ 高并发写入场景未评估清楚
迁移前必须搞清:
□ 数据量、增长速率
□ 上下游依赖(应用、BI、同步管道)
□ 可接受的停机窗口(决定方案)
□ 业务高峰时段(定切换时机)
记忆:迁移是「工程」不是「搬运」——先算清差异与停机预算,再谈方案。
2. 兼容性差异矩阵:评估先行
建一张差异清单逐项核对,避免迁移期才发现:
| 维度 | MySQL | PostgreSQL | 注意 |
|---|---|---|---|
| 字符串长度 | varchar 按字符 | text/varchar | PG 不限长度,超长改 text |
| 自增 | AUTO_INCREMENT | IDENTITY/serial | 语法差异 |
| 布尔 | TINYINT(1) | boolean | 类型映射 |
| 时间 | TIMESTAMP(时区麻烦) | timestamptz | 建议统一 UTC |
| 全文 | FULLTEXT | tsvector + GIN | 语法完全不同 |
| 插入冲突 | INSERT ... ON DUPLICATE KEY | ON CONFLICT | 语义需核对 |
| 大小写 | 表名可大小写混用 | 一律小写(除非引号) | 迁移时统一小写 |
| JSON | JSON(文本存储) | jsonb(二进制) | 映射后查询语法差异 |
评估方法:写一个「schema + 代表查询」的兼容性对照脚本,在新库跑一遍代表业务查询。
3. 三种迁移方案与选型
| 方案 | 原理 | 停机 | 适用 |
|---|---|---|---|
| 逻辑导出导入 | mysqldump/pg_dump + 转换 | 需停机 | 小库、可停机 |
| ETL 工具 | pgloader / 自研脚本 | 短停机 | 中大型、有结构转换 |
| 双写 + CDC | 应用双写 + Debezium | 零停机 | 大型、生产在线 |
方案对比:
□ 停机迁移:简单、风险集中、适合可接受几分钟停机的小系统
□ pgloader:专为 MySQL→PG 设计,自动类型映射
□ 双写 CDC:零停机但工程量大(改应用 + 部署同步管道)
决策:先看停机预算——预算够用 pgloader/导出导入;预算为零必须双写 CDC。
4. MySQL→PostgreSQL:类型与语法映射
类型映射表(pgloader 自动做,手工迁移参考):
| MySQL | PostgreSQL |
|---|---|
TINYINT/SMALLINT | smallint |
INT/INTEGER | integer |
BIGINT | bigint |
VARCHAR(n) | varchar(n) / text |
TEXT | text |
DATETIME/TIMESTAMP | timestamptz |
DATE | date |
DECIMAL(p,s) | numeric(p,s) |
TINYINT(1) | boolean |
BLOB | bytea |
JSON | jsonb |
自增列迁移:
-- 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 必须改的地方):
| 场景 | MySQL | PostgreSQL |
|---|---|---|
| 分页限制 | LIMIT 10 OFFSET 20 | 相同(LIMIT/OFFSET) |
| 插入冲突 | ON DUPLICATE KEY UPDATE | ON CONFLICT (id) DO UPDATE SET ... |
| 字符串拼接 | CONCAT(a,b) / `a | |
| 逻辑或 | ` | |
| 布尔 | WHERE active = 1 | WHERE 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 映射 | 说明 |
|---|---|---|
| Collection | Table | 结构化的集合 |
| 内嵌文档 | jsonb 列 或 拆分表 | 弱结构内嵌用 jsonb |
| 数组字段 | text[]/jsonb 或关联表 | 简单数组用数组列 |
| 动态字段 | jsonb | 保留扩展性 |
_id ObjectId | uuid/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 数据库专题
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。