MySQL→PostgreSQL 主从架构迁移方案(读写分离版)
Posted on Thu 09 October 2025 in 数据库
目标:用 PostgreSQL 的 WAL + Streaming Replication 实现“写走主、读走从”,并提供生产可用的高可用与连接层方案,附配置模板与运维脚本示例。适配 PostgreSQL 16/17/18。
1. 架构总览
1.1 基础拓扑(最小可用)
App(写) ─────────► Primary(主)
╲
╲ WAL Stream
╲
App(读) ───────────► Standby1(从)
► Standby2(从)
- 写请求:直连主库。
- 读请求:直连从库(或通过中间层,见 §4)。
- 主从:物理复制(Streaming Replication),异步或半同步可选。
1.2 生产级拓扑(推荐)
+-------------------+
| pgbouncer | 连接池(减少连接抖动)
+-------------------+
│
+--------------+
| Pgpool-II | SQL解析级读写分离/健康检查/故障转移脚本
+--------------+
│ │
(Write) (Read)
│ │
Primary ──┬── Standby1
└── Standby2
+-------------------+
| Patroni + etcd | 主从编排/自动故障切换/仲裁
+-------------------+
- 可替换 Pgpool-II 为 HAProxy(协议层转发)+ 应用侧读写分离(双 DSN)。
- 可替换 Patroni 为 repmgr 或手工脚本(风险更高)。
2. 版本与参数基线
- 推荐版本:PostgreSQL 17(当前成熟稳定)或 18(新项目/前瞻特性)。
- 最小参数:
wal_level = replica(或logical若需逻辑复制)max_wal_senders≥ 从库数 + 维护冗余(例如 10)wal_keep_size:按网络/故障时间留足(例如 512MB~2GB)archive_mode = on+archive_command(若做增量备份/回放)- 半同步:
synchronous_commit = on+synchronous_standby_names = 'FIRST 1 (standby1, standby2)'
3. 数据层:主从复制配置模板(物理复制)
3.1 主库 postgresql.conf
# 基础
listen_addresses = '*'
port = 5432
# 复制/WAL
wal_level = replica
max_wal_senders = 10
max_wal_size = 4GB
min_wal_size = 1GB
wal_keep_size = 1024MB
# 半同步(可选)
# synchronous_commit = on
# synchronous_standby_names = 'FIRST 1 (standby1, standby2)'
# 性能观测(推荐开启)
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
# 归档(如采用 pgBackRest 则由其接管)
# archive_mode = on
# archive_command = 'test ! -f /var/backup/%f && cp %p /var/backup/%f'
3.2 主库 pg_hba.conf
# 允许业务访问
host all appuser 10.0.0.0/16 scram-sha-256
# 允许复制连接(repl 角色)
host replication repl 10.0.0.0/16 scram-sha-256
3.3 创建复制用户
CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'StrongReplPass!';
3.4 从库基线拉起(pg_basebackup)
在 Standby 节点执行:
pg_basebackup -h 10.0.0.10 -p 5432 -U repl \
-D /var/lib/postgresql/17/data -X stream -C -S standby1_slot -R -P
-R:自动写入standby.signal与primary_conninfo。-C -S standby1_slot:自动创建复制槽,防止 WAL 丢失。- 多个从库请使用不同 slot 名称:
standby2_slot等。
3.5 启动与验证
- 启动从库后,在主库查看:
SELECT pid, application_name, client_addr, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;
- 从库延迟:
SELECT now() - pg_last_xact_replay_timestamp() AS standby_delay;
3.6 延迟从库(防误删回滚保护,可选)
从库 postgresql.auto.conf 增加:
recovery_min_apply_delay = '5min'
4. 连接层:读写分离方案
4.1 Pgpool-II(SQL 解析级,自动区分读/写)
核心优点
- 会解析 SQL 并将 SELECT 分发到从库,将 写语句 路由到主库;
- 内置健康检查、自动跟随主库(
follow_primary_command),可脚本化故障切换; - 与 pgbouncer 叠加实现“连接池 + 读写分离”。
4.1.1 pgpool.conf 样例(核心片段)
# 监听
listen_addresses = '0.0.0.0'
port = 9999
# 后端节点(node_id 从 0 开始)
backend_hostname0 = '10.0.0.10' # primary
backend_port0 = 5432
backend_weight0 = 1
backend_flag0 = 'ALWAYS_PRIMARY'
backend_hostname1 = '10.0.0.11' # standby1
backend_port1 = 5432
backend_weight1 = 1
backend_flag1 = 'ALLOW_TO_FAILOVER'
backend_hostname2 = '10.0.0.12' # standby2
backend_port2 = 5432
backend_weight2 = 1
backend_flag2 = 'ALLOW_TO_FAILOVER'
# 读写分离与负载
load_balance_mode = on # 允许 SELECT 负载到从库
statement_level_load_balance = on # 语句级平衡(谨慎与事务特性)
replication_mode = off # 非多主
master_slave_mode = on
master_slave_sub_mode = 'stream' # Streaming Replication
# 健康检查
health_check_period = 5
health_check_timeout = 3
health_check_user = 'pgpool' # 仅用于健康检查的低权账号
health_check_password = '***'
# 主从跟随(当主切换时,更新路由)
follow_primary_command = '/usr/local/bin/pgpool-follow-primary %d %h %p %D %m %H %M %P %r %R'
# 会话保持与函数黑名单(避免副作用语句被当作读)
black_function_list = 'nextval,setval'
white_function_list = ''
# 日志
log_per_node_statement = off
log_statement = off
4.1.2 pool_hba.conf
host all all 10.0.0.0/16 scram-sha-256
4.1.3 pcp.conf
admin:SCRAM-SHA-256$<hash>
4.1.4 典型运维脚本占位
pgpool-follow-primary:当检测到新主库后,自动调整后端主从角色并重载。failover_streaming:在主库失效时,触发预设的 Promote(若未用 Patroni)。
注:Pgpool-II 与 prepared statements / 长事务 叠加时需评估;对强一致读,可将关键读强制走主(应用层或
app_name路由)。
4.2 HAProxy + pgbouncer(轻量级)
- 策略:对应用提供两个 DSN:
- 写:
haproxy-write:5432(仅指向主库或 Patroni 的主 VIP) - 读:
haproxy-read:5432(轮询多个从库) - HAProxy 核心示例:
frontend pg_write
bind *:5000
default_backend pg_primary
backend pg_primary
option tcp-check
server primary 10.0.0.10:5432 check
frontend pg_read
bind *:5001
default_backend pg_standbys
backend pg_standbys
balance roundrobin
option tcp-check
server s1 10.0.0.11:5432 check
server s2 10.0.0.12:5432 check
- pgbouncer:建议 transaction 池化模式,注意与
server_reset_query配置,避免事务泄漏。
5. 高可用:Patroni + etcd(推荐)
5.1 部署要点
- 各节点运行 Patroni,使用 etcd(或 Consul)存储集群状态;
- Patroni 负责:
- 主从编排、健康检查、自动 promote;
- 维护
postgresql.conf与复制参数; - 提供 REST API(供 HAProxy/keepalived 识别主)。
5.2 Patroni 样例配置(/etc/patroni/pg01.yml)
scope: pg-cluster
name: pg01
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.0.10:8008
etcd:
hosts: 10.0.0.20:2379,10.0.0.21:2379,10.0.0.22:2379
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.0.10:5432
data_dir: /var/lib/postgresql/17/data
bin_dir: /usr/lib/postgresql/17/bin
parameters:
wal_level: replica
max_wal_senders: 10
wal_keep_size: 1024MB
shared_preload_libraries: pg_stat_statements
authentication:
replication:
username: repl
password: StrongReplPass!
superuser:
username: postgres
password: StrongSuperPass!
pg_hba:
- host all all 0.0.0.0/0 scram-sha-256
- host replication repl 0.0.0.0/0 scram-sha-256
synchronous_mode: false # 如需半同步置 true,并配置 standby 优先级
- 其他节点将
name与connect_address对应修改即可。
5.3 与 HAProxy 集成(识别主)
HAProxy 可基于 Patroni REST 的 /master endpoint 进行后端切换,或以 tag 端口代理当前主。
6. 运维脚本与常见操作
6.1 手工主从切换(无 Patroni 场景)
- 在待升主的从库执行 Promote:
pg_ctl -D /var/lib/postgresql/17/data promote
- 调整应用/中间件连接到新主。
- 原主修复后以从库身份重拉:
pg_ctl -D /var/lib/postgresql/17/data stop
rm -rf /var/lib/postgresql/17/data/*
pg_basebackup -h NEW_PRIMARY -U repl -D /var/lib/postgresql/17/data -X stream -R -P -C -S oldprimary_slot
pg_ctl -D /var/lib/postgresql/17/data start
6.2 创建/查看复制槽
-- 创建逻辑槽(如需逻辑复制)
SELECT * FROM pg_create_logical_replication_slot('app_slot', 'pgoutput');
-- 查看复制槽
SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots;
6.3 只读事务(强一致读在主库)
BEGIN READ ONLY; -- 强制在主库读(通过路由或角色策略)
SELECT ...;
COMMIT;
7. 监控与告警
7.1 关键视图
-- 主库查看复制
SELECT * FROM pg_stat_replication;
-- WAL 生成量
SELECT date_trunc('hour', now()) AS ts,
pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')/1024/1024/1024 AS wal_gb;
-- 从库延迟
SELECT now() - pg_last_xact_replay_timestamp() AS standby_delay;
7.2 Prometheus Exporter(建议)
postgres_exporter:抓取一般指标pgbouncer_exporter、pgpool2_exporter(若使用)- 告警建议:
standby_delay > 5s/30s/60s分级;replication_state != streamingreplication_slot_inactive& WAL 堆积
8. 备份与恢复
8.1 工具选型
- pgBackRest(推荐):全量/增量/差异、并行、校验、保留策略、S3 对接;
pg_basebackup+ WAL 归档:适合轻量场景。
8.2 pgBackRest 最小配置示例(片段)
/etc/pgbackrest/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=7
start-fast=y
[pg]
pg1-path=/var/lib/postgresql/17/data
pg1-port=5432
- 调度:
pgbackrest backup --type=full|diff|incr - 恢复:
pgbackrest restore --type=time --target='2025-10-08 12:00:00'
9. 逻辑复制(可选:按表级/跨版本)
9.1 主库发布(publication)
CREATE PUBLICATION mypub FOR TABLE public.users, public.orders;
9.2 从库订阅(subscription)
CREATE SUBSCRIPTION mysub
CONNECTION 'host=10.0.0.10 dbname=app user=repl password=StrongReplPass!'
PUBLICATION mypub;
适用:选择性同步、跨版本/异构聚合、CDC 下游(Kafka/ETL)。
10. 应用侧改造要点(从 MySQL 迁移)
- 自增主键:改为
GENERATED BY DEFAULT AS IDENTITY; - UPSERT:
INSERT ... ON CONFLICT (key) DO UPDATE; - 读写分离:强一致读上主(关键交易/下单等),弱一致读走从;
- 事务隔离:PostgreSQL 默认
READ COMMITTED,必要时用REPEATABLE READ; - SQL 差异:
LIMIT/OFFSET、BOOLEAN、text/varchar、ILIKE等; - 连接池:强烈建议使用 pgbouncer(transaction 模式)。
11. 交付清单(可直接使用/改造)
- 数据层:
postgresql.conf、pg_hba.conf基线模板(§3) - 初始化:
pg_basebackup命令(§3.4) - 验证脚本:
pg_stat_replication/延迟检查 SQL(§3.5、§7) - 中间层:Pgpool-II 样例(§4.1)或 HAProxy 样例(§4.2)
- 高可用:Patroni 示例(§5.2)
- 故障切换:Promote/重拉流程(§6.1)
- 备份:pgBackRest 样例(§8.2)
12. 风险与最佳实践
- WAL 保留不足:务必使用复制槽或足够的
wal_keep_size,防止从库追不上; - 读写错路由:关键读务必走主;Pgpool 解析有边界,建议灰度与压测;
- 长事务:阻塞 VACUUM,导致膨胀与复制延迟;监控
pg_stat_activity; - 半同步权衡:开启会增加写延迟;金融交易强一致可
FIRST 1策略; - 备份演练:每季度至少一次异地恢复演练;
- 版本升级:用
pg_upgrade --check预检测,读 release notes。
13. 附:Docker Compose(演示用)
仅供演示,生产请改为有状态存储 + 独立网络与安全策略。
version: '3.8'
services:
primary:
image: postgres:17
environment:
POSTGRES_PASSWORD: StrongSuperPass!
ports:
- '5432:5432'
volumes:
- primary_data:/var/lib/postgresql/data
standby1:
image: postgres:17
depends_on:
- primary
environment:
POSTGRES_PASSWORD: StrongSuperPass!
command: >
bash -lc "pg_basebackup -h primary -U postgres -D /var/lib/postgresql/data -R -P && docker-entrypoint.sh postgres"
volumes:
- standby1_data:/var/lib/postgresql/data
volumes:
primary_data: {}
standby1_data: {}
结语
本方案覆盖“写主读从”的完整链路:复制 → 连接层 → 高可用 → 备份 → 监控。可按你现网规模裁剪。如需,我可以基于你的主机清单/网段/端口策略,生成一套可直接上线的配置包(含脚本与 Ansible 角色)。