PostgreSQL逻辑复制(Logical Replication)是数据库高可用架构中的核心组件,通过解析WAL(Write-Ahead Log)日志中的逻辑变更,实现跨实例的数据同步。与物理复制不同,逻辑复制支持选择性复制表、跨大版本复制和异构架构复制,在数据迁移实战、读写分离和实时数仓同步场景中应用广泛。
逻辑复制与物理复制的区别
物理复制基于WAL字节流,从节点是主节点的完整二进制副本,不支持选择性复制,要求主从版本一致、架构一致。逻辑复制将WAL解码为逻辑变更事件(INSERT/UPDATE/DELETE),从节点可以只复制部分表,版本和架构可以不同。
特性 | 物理复制 | 逻辑复制 复制粒度 | 整个实例 | 表级别选择性复制 版本要求 | 主从版本必须一致 | 可跨大版本 架构要求 | 必须相同 | 可跨平台/架构 复制内容 | 所有数据文件+WAL | 仅DML操作(INSERT/UPDATE/DELETE) DDL同步 | 自动同步 | 不支持,需手动操作 从节点可写 | 否(只读) | 是(可写) 典型用途 | 高可用容灾 | 数据同步/迁移/读写分离
发布端配置与Publication创建
逻辑复制需要将PostgreSQL的wal_level参数设置为logical,并调整相关参数确保WAL包含足够的逻辑解码信息。
-- postgresql.conf 配置 wal_level = logical max_wal_senders = 10 max_replication_slots = 10 wal_sender_timeout = 60s -- 重载配置(需要重启才能生效wal_level) -- systemctl restart postgresql -- 创建复制用户 CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'Str0ngP@ss!'; -- 在发布端创建Publication -- 复制所有表 CREATE PUBLICATION pub_all FOR ALL TABLES; -- 复制指定表 CREATE PUBLICATION pub_ycms FOR TABLE users, articles, categories; -- 复制指定表且仅复制特定操作 CREATE PUBLICATION pub_insert_only FOR TABLE logs WITH (publish = 'insert'); -- 查看Publication状态 SELECT * FROM pg_publication; SELECT * FROM pg_publication_tables; -- 向Publication添加表 ALTER PUBLICATION pub_ycms ADD TABLE comments; -- 从Publication移除表 ALTER PUBLICATION pub_ycms DROP TABLE comments;
发布端需要确保复制的表有主键或唯一索引,逻辑复制通过主键标识行进行UPDATE和DELETE操作同步。没有主键的表只能复制INSERT操作。
-- 检查表是否有复制标识
SELECT relname, relreplident
FROM pg_class
WHERE relname IN ('users', 'articles', 'categories');
-- relreplident 值说明:
-- 'd' = 默认(主键或唯一索引)
-- 'p' = 仅主键
-- 'n' = 无标识(仅支持INSERT复制)
-- 'f' = 指定列
-- 为没有主键的表设置复制标识
ALTER TABLE logs REPLICA IDENTITY FULL;
-- FULL模式记录所有列的旧值,支持UPDATE/DELETE但日志量增大
订阅端配置与Subscription创建
订阅端需要先创建与发布端结构相同的表,然后创建Subscription连接发布端的Publication。逻辑复制不支持DDL同步,表结构变更需要手动在两端执行。
-- 订阅端创建表(结构与发布端一致)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
email VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
);
-- 创建Subscription
CREATE SUBSCRIPTION sub_ycms
CONNECTION 'host=192.168.1.10 port=5432 dbname=ycms user=replicator password=Str0ngP@ss!'
PUBLICATION pub_ycms;
-- 带参数创建Subscription
CREATE SUBSCRIPTION sub_ycms
CONNECTION 'host=192.168.1.10 port=5432 dbname=ycms user=replicator password=Str0ngP@ss!'
PUBLICATION pub_ycms
WITH (
copy_data = true, -- 初始同步时复制现有数据
create_slot = true, -- 自动创建复制槽
slot_name = 'sub_ycms_slot',
enabled = true,
synchronous_commit = off -- 异步提交提高吞吐
);
-- 查看Subscription状态
SELECT * FROM pg_subscription;
SELECT * FROM pg_stat_subscription;
-- 监控同步进度
SELECT
subname,
received_lsn,
latest_end_lsn,
latest_end_time,
now() - latest_end_time AS lag
FROM pg_stat_subscription;
pg_stat_subscription视图中的received_lsn和latest_end_lsn之间的差距反映了复制延迟。lag值接近0表示同步正常,lag持续增长需要排查网络延迟和从节点处理能力。
复制槽管理与WAL堆积问题处理
复制槽(Replication Slot)确保主节点在从节点确认接收前不删除WAL日志。如果从节点长时间断开,WAL会持续堆积导致磁盘写满。需要监控复制槽状态并及时处理异常。
-- 查看所有复制槽
SELECT slot_name, plugin, slot_type, active, restart_lsn
FROM pg_replication_slots;
-- 查看WAL堆积量
SELECT
slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_size,
active,
active_pid
FROM pg_replication_slots;
-- 临时禁用订阅(保留复制槽和数据)
ALTER SUBSCRIPTION sub_ycms DISABLE;
-- 启用订阅
ALTER SUBSCRIPTION sub_ycms ENABLE;
-- 删除订阅(同时删除复制槽)
DROP SUBSCRIPTION sub_ycms;
-- 手动删除卡住的复制槽
SELECT pg_drop_replication_slot('sub_ycms_slot');
配置WAL保留策略和告警阈值:
-- postgresql.conf 中设置 max_slot_wal_keep_size
-- 限制单个复制槽保留的WAL最大量
max_slot_wal_keep_size = '10GB'
-- 监控脚本(定期执行)
-- 检查非活跃复制槽的WAL堆积
SELECT
slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_size,
active,
CASE
WHEN NOT active THEN 'INACTIVE - Check needed'
ELSE 'OK'
END AS status
FROM pg_replication_slots
WHERE NOT active
OR pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) > 1073741824; -- > 1GB
跨版本复制与数据迁移方案
逻辑复制支持跨大版本复制(如PG 13到PG 16),这是数据迁移实战中零停机迁移的核心方案。迁移流程为先建立逻辑复制保持数据同步,在业务低峰期切换应用连接到新版本实例。
-- 零停机迁移流程(PG 13 -> PG 16) -- 1. 新版本PG 16实例创建与旧版本相同的表结构 -- 使用 pg_dump 导出表结构(不含数据) pg_dump -h old-pg13 -U postgres --schema-only --no-owner ycms > schema.sql -- 2. 在PG 16上导入表结构 psql -h new-pg16 -U postgres -d ycms < schema.sql -- 3. 在PG 13上创建Publication -- 需先将wal_level改为logical并重启 CREATE PUBLICATION migrate_pub FOR ALL TABLES; -- 4. 在PG 16上创建Subscription CREATE SUBSCRIPTION migrate_sub CONNECTION 'host=old-pg13 port=5432 dbname=ycms user=replicator password=Str0ngP@ss!' PUBLICATION migrate_pub WITH (copy_data = true); -- 5. 等待初始数据同步完成 -- 监控同步状态 SELECT * FROM pg_stat_subscription WHERE subname = 'migrate_sub'; -- 确认 latest_end_lsn 不再变化且 lag 接近 0 -- 6. 验证数据一致性 -- 在两端执行行数对比 SELECT count(*) FROM users; -- 两端结果应一致 -- 7. 业务切换(维护窗口内) -- 应用配置改为连接新PG 16实例 -- 旧实例可降级保留作为回滚备份 -- 8. 清理 DROP SUBSCRIPTION migrate_sub; -- 在PG 16上执行 -- 在PG 13上 DROP PUBLICATION migrate_pub;
跨版本迁移时需要注意PG版本间的默认参数变化和兼容性差异。例如PG 14+的非空列不再自动创建B树索引,PG 15+的sort性能优化可能影响查询计划。在迁移前应在新版本上运行完整测试套件验证应用兼容性。
逻辑复制冲突处理与运维最佳实践
逻辑复制冲突发生在订阅端的数据状态与发布端的变更不兼容时,常见原因包括:订阅端表被外部修改、主键冲突、约束违反。冲突发生时复制会暂停,需要手动解决后恢复。
-- 查看复制冲突日志 SELECT * FROM pg_stat_subscription WHERE subname = 'sub_ycms'; -- 常见冲突处理方式 -- 1. 主键冲突:删除订阅端冲突数据 DELETE FROM users WHERE id = 1234; -- 删除后复制自动恢复 -- 2. 约束冲突:临时放宽约束 ALTER TABLE users ALTER COLUMN email DROP NOT NULL; -- 复制恢复后再根据需要恢复约束 -- 3. 跳过冲突事务(PG 16+) ALTER SUBSCRIPTION sub_ycms SKIP (lsn = '0/12345678');
运维最佳实践包括:定期检查pg_stat_subscription的复制延迟;监控复制槽的WAL堆积量;订阅端避免对复制表的直接写入(设置触发器或权限控制);在主节点配置合适的max_slot_wal_keep_size防止磁盘写满。结合pgBouncer或连接池中间件管理复制连接,避免连接数过多影响主节点性能。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/postgresql-luo-ji-fu-zhi-shi-zhan-fa-bu-ding-yue-pei-zhi-yu/