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.signalprimary_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 优先级
  • 其他节点将 nameconnect_address 对应修改即可。

5.3 与 HAProxy 集成(识别主)

HAProxy 可基于 Patroni REST 的 /master endpoint 进行后端切换,或以 tag 端口代理当前主。


6. 运维脚本与常见操作

6.1 手工主从切换(无 Patroni 场景)

  1. 在待升主的从库执行 Promote:
pg_ctl -D /var/lib/postgresql/17/data promote
  1. 调整应用/中间件连接到新主。
  2. 原主修复后以从库身份重拉:
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_exporterpgpool2_exporter(若使用)
  • 告警建议:
  • standby_delay > 5s/30s/60s 分级;
  • replication_state != streaming
  • replication_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/OFFSETBOOLEANtext/varcharILIKE 等;
  • 连接池:强烈建议使用 pgbouncer(transaction 模式)。

11. 交付清单(可直接使用/改造)

  • 数据层:postgresql.confpg_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 角色)。