做数据库迁移这事儿,最怕什么?最怕业务停摆。尤其是生产环境里跑着核心业务的 PostgreSQL,哪怕断个一两分钟,用户投诉、老板拍桌子,运维就得背锅。但现实往往逼着你不得不迁——机房要搬、版本要升级、硬件要扩容,甚至要从自建机房搬到云上。我今天聊的就是怎么把这种“大手术”做成“微创”,甚至让业务方完全没有感觉。

说个真实场景。去年帮一家电商公司做迁移,数据库 12 TB,每天还有几百万订单写入。传统做法是停机备份、恢复、校验,少说也得四五个小时。但人家双十一刚过,老板说“你敢停半小时试试”。我们采用的方案是逻辑复制加流复制双保险,从开始同步到最终切换,业务端零感知。核心思路只有一个:别把数据库当黑盒,要把它当成可以拆装的活动零件。
具体怎么拆?第一步是确认迁移路径。PostgreSQL 支持两种主流方案:逻辑复制和流复制。流复制适合同版本、同架构的迁移,延迟低、一致性高,但要求主备库操作系统和硬件架构完全一致。逻辑复制更灵活,可以跨大版本、跨操作系统,甚至从 PostgreSQL 迁到云数据库。我建议首选流复制,实在受限再使用逻辑复制。比如要从 CentOS 迁到 Ubuntu,或者从 PG 10 跳到 PG 16,那只能走逻辑复制。
定好方案后,开始搭环境。这里有个坑很多人踩——以为搭好复制就万事大吉。流复制配置其实不难:主库打开 (或 ),创建复制账号,备库用 拉数据,然后配置 。真正要命的是网络延迟和磁盘 I/O。我曾碰到备库 I/O 被打满,导致复制延迟飙到十几秒,查了半天才发现是备库同时跑着离线分析任务。所以迁移期间,备库除了接收 WAL 日志,最好别干别的活。
逻辑复制更考验耐心。它基于发布‑订阅机制,需要先在源库创建发布,再在目标库创建订阅。但有个致命问题:初始同步时,如果表上有大事务未提交,订阅会一直卡住。我见过一个案例,某张表上有条长达 8 小时的未提交事务,逻辑复制直接停了 7 小时。解决办法是先把大事务终止,或者等它提交后再建订阅。另外,DDL 操作逻辑复制不自动同步,需要手动处理表结构变更,这也是大坑。
数据同步稳定后,进入灰度切换阶段。千万别直接切断源库,得先做“预热切换”——把少量读流量切到目标库,观察应用层反馈。比如先让报表查询走新库,核心写入仍留在旧库。这个阶段要盯几个指标:新库的查询延迟、连接数变化、是否出现死锁。我遇到过最夸张的情况是,新库因为索引没建对,一个简单查询跑了 3 秒,业务直接报错。所以灰度期间,应用层的 SQL 日志一定要打开,哪个慢就抓哪个。
灰度跑了一两天,确认没问题后,才进入正式切换环节。这里有个技巧:别手动切,用脚本自动化。我习惯写个 Python 脚本,先停掉应用层连接,然后检查主备延迟是否在 100 毫秒以内,接着把源库设为只读,确认目标库的复制已追上,最后修改 DNS 或连接池配置。整个过程控制在 30 秒内完成。如果中间任何一步失败,脚本会自动回滚,把连接切回源库。这样即使出问题,业务也只会感知到一次短暂抖动,而不是长时间断连。
平滑切换只是开始,真正的考验在后面。很多团队迁移完就以为完事了,结果第二天发现新库的查询计划和旧库不一样,某些 SQL 从毫秒级变成秒级。原因很简单:统计信息没有及时更新。迁移后必须跑一遍 ,最好再把所有核心表的统计信息重新收集一次。另外,新库的配置参数也要调——比如 、,千万别照搬旧库,得根据新机器重新评估。
还有更隐蔽的坑:序列号。PostgreSQL 的序列默认不通过 WAL 复制,所以逻辑复制后的新库,序列可能比旧库落后很多。如果用序列做主键,新库插入时会报主键冲突。解决办法是在切换前手动把序列调到最大值,或者使用 列代替序列。我一般在切换脚本里加一步:把所有序列 到当前最大值加 100,留足缓冲。
给个忠告:别迷信“零停机”。再完美的方案也扛不住极端情况——比如光纤被挖断、目标库磁盘写满。真正靠谱的做法是设计好回滚路径。我每次迁移都会保留源库的读写能力至少 72 小时,万一新库崩了,只要把 DNS 切回去就能恢复。而且回滚脚本一定要提前写好,别等到出问题再手忙脚乱。
总结下来,PostgreSQL 迁移的核心就三件事:选对复制方案、盯住延迟指标、备好回滚预案。每一步都别想当然,用数据说话、用脚本验证。做到这些,零停机就不是玄学,而是可复现的工程实践。下次再接到迁移任务时,别慌,按这个流程走一遍,大概率能睡个安稳觉。


