Whoami:5年+金融、政府、医疗领域工作经验的DBACertificate:OCP、PCPSkill:Oracle、Mysql、PostgreSQLPlatform:CSDN、墨天伦、公众号(呆呆的私房菜)
阅读本文可以了解到逻辑复制概念、复制原理、应用场景、使用限制、实战案例以及使用过程中的常见问题及处理方式等内容。
逻辑复制的历史:
2014年发布的PostgreSQL 9.4版本开始就支持逻辑复制了,但是没有将其引入内核。
逻辑复制是PostgreSQL 10版本重量级新特性,支持内置的逻辑复制;
在PostgreSQL 10之前的版本,虽然没有内置的逻辑复制,但是也可以通过其他方式实现(如触发器、自定义脚本等实现表级别同步,还有外部工具londsite3实现)。

逻辑复制是什么?
逻辑复制是一种基于数据对象的复制标识(通常是主键)复制数据对象及其更改的方法。
逻辑复制使用一种发布和订阅模型,其中有一个或多个订阅者订阅一个发布者节点上的一个或多个发布。订阅者从他们所订阅的发布拉取数据并且可能后续重新发布这些数据以允许级联复制或者更复杂的配置。
逻辑复制和物理复制都是基于wal日志实现的,同一个数据库实例可以同时使用逻辑复制和物理复制。

PostgreSQL的逻辑复制基于wal日志和复制槽的机制。
主节点将其publication中的表的wal日志进行解析后,形成一种特殊的日志流,传给从节点。
从节点上的subscription对这些日志流进行解析重返,从而达到同步表数据的功能。
两者wal的传输是通过各自服务器上的wal sender和wal receiver进程来完成的。
注意:逻辑复制它复制的是SQL操作的结果,而不是SQL本身。
复制槽:提供了一种自动化的方法来确保主库在所有的备库收到wal记录之前不会移除它们,并且主库也不会移除可能导致恢复冲突的行,即使备库断开也是如此。
select * from pg_replication_slots;
注意:逻辑复制的功能也在不断地完善,pg16版本目前已经支持双向复制了。
1. 发布端:
# 设置日志模式wal_level=logical# 指定复制槽数max_replication_slots# 设置wal日志发送进程数量max_wal_senders
2. 订阅端:
# 指定逻辑复制工作的最大数据量,默认是4max_logical_replication_workers# 指定每个订阅最大同步工作者数量,默认是2max_sync_workers_per_subscriptio# 指定工作进程数max_worker_processes
1. 发布:
可以在任何物理复制主机上定义发布,定义发布的节点称为发布者;
每个发布只存在于一个数据库中;
发布者是一个表或一组表中生成的一组更改,也可能被描述为更改集或者复制集;
发布与schema不同,不影响表格的访问方式。如果需要,每张表可以添加到多个发布;
发布可以选择将它们所产生的改变限制在insert/update/delete的任意组合上,类似于触发器。默认情况下复制所有操作类型。
2. 订阅:
订阅是逻辑复制的下游端,定义订阅的节点称为订阅者;
订阅定义了与另一个数据库的连接以及它想要订阅的一个或一组发布;
订阅者数据库可以通过定义自己的发布来作为其他数据库的发布者;一个订阅者节点可以有多个订阅;
每个订阅都将通过一个复制槽接受更改;
逻辑复制订阅可以作为同步复制的备用数据库。
下面我们看看订阅发布的常用命令:
# 发布订阅实例1. 创建一个发布,发布两个表中的所有修改create publication p1 for table t1, t2;2. 创建一个发布,发布所有表中的所有更改create publication allp for all tables;3. 创建一个发布,只发布一个表中的insert操作create publication insertp for table t3 with (publish='insert');4. 将发布修改为只发布删除和修改alter publication noinsertp set (publish='update,delete');5. 给发布添加一些表alter publication p1 add table t4, t5;6. 查看发布select * from pg_publicatin;# 订阅常用命令1. 创建一个到远程服务器的订阅,复制发布p1和insertp中的表(即t1,t2,t3),并在提交时立即复制create subscription s1connection 'host=10.0.0.21 port=5432 user=test dbname=testdb'publication p1, insertp;2. 创建一个到远程服务器的订阅,复制发布insertp中的表,并且不开始复制直到启用复制create subscription s2connection 'host=10.0.0.21 port=5432 user=test dbname=testdb'publication insertp with (enabled=false);3. 更改订阅发的发布:alter subscription s1 set publication insertp;4. 停止订阅alter subscription s1 disable;
原生逻辑复制使用限制:
不支持ddl复制(create table alter table);
不支持temporary表和unlogged表复制;
不支持sequences复制(serial bigserial identity);
不支持大对象复制(bytea);
不支持视图、物化视图、外部表复制。
pglogical逻辑复制插件使用限制:
数据库版本限制:发布和订阅节点需要运行 PostgreSQL 9.4+;
复制源过滤和冲突检测需要 PostgreSQL 9.5+。
一、环境说明1. 发布端:postgres=# select version();version---------------------------------------------------------------------------------------------------------PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit(1 行记录)2. 订阅端:postgres=# select version();version---------------------------------------------------------------------------------------------------------PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit(1 行记录)二、环境准备1. 发布端配置参数vi $PGDATA/postgresql.conflisten_addresses = '*'wal_level = logicalmax_replication_slots=102. 订阅端配置参数vi $PGDATA/postgresql.confmax_logical_replication_workers=2max_sync_workers_per_subscription=23. 创建同步数据库和用户create user repl replication login connection limit 10 encrypted password 'repl';4. 模拟业务用户和表create user test;create database testdb owner test;create schema testdb authorization test;# 创建表1create table testdb.t1 (id int);insert into testdb.t1 values (1),(2),(3);5. 授予同步用户权限grant connect on database testdb to repl;grant usage on schema testdb to repl;grant select on testdb.t1 to repl;\dp+ t1;三、创建发布testdb=> create publication p1 for table testdb.t1;CREATE PUBLICATIONtestdb=> select * from pg_publication;oid | pubname | pubowner | puballtables | pubinsert | pubupdate | pubdelete | pubtruncate | pubviaroot-------+---------+----------+--------------+-----------+-----------+-----------+-------------+------------16430 | p1 | 16413 | f | t | t | t | t | f(1 行记录)四、创建订阅1. 创建接收表create user test;create database testdb owner test;create schema testdb authorization test;create table testdb.t1 (id int);2. 创建订阅postgres=# create subscription s1 connection 'host=10.28.12.21 port=5432 dbname=testdb user=repl' publication p1;NOTICE: created replication slot "s1" on publisherCREATE SUBSCRIPTIONpostgres=# select * from pg_subscription;oid | subdbid | subskiplsn | subname | subowner | subenabled | subbinary | substream | subtwophasestate | subdisableonerr | subpasswordrequired | subrunasowner | subconninfo | subslotname | subsynccommit | subpublications | suborigin-------+---------+------------+---------+----------+------------+-----------+-----------+------------------+-----------------+---------------------+---------------+----------------------------------------------------+-------------+---------------+-----------------+-----------16397 | 5 | 0/0 | s1 | 10 | t | f | f | d | f | t | f | host=10.28.12.21 port=5432 dbname=testdb user=repl | s1 | off| {p1} | any(1 row)五、测试同步1. 发布端:testdb=> select * from testdb.t1;id----123(3 行记录)testdb=> insert into testdb.t1 values (4);INSERT 0 12. 目标端postgres=# select * from testdb.t1;id----1234(4 rows)
1. failover slot:生产库一般都会构建主备架构,主库故障后,从库接管服务了,但是却没有相应的复制槽信息,也就是缺少failover slot。这是由于保存slot信息的物理文件未同步到备库。
解决方案:1. 主库创建复制槽,检查备库wal文件是否连续;2. 主库复制$PGDATA/pg_repslot包含slot信息的物理文件到备库;3. 备库重启,重启后才可以看到复制槽信息,原因是读取slot物理文件的函数只会在postmaster进程启动时调用;4. 定期查询主库slot状态,使用pg_replication_slot_advance函数推进备库复制槽
2. ddl同步:原生的逻辑复制不支持复制ddl语句。
解决方案:使用事务触发器来处理1. 使用事件触发器记录变动的表结构,记录到ddl_change表中,并将该表通过逻辑复制进行发布;2. 订阅端接收到该表的数据变更后,处理为相应的ddl语句去执行。
3. toast问题:
解决方案:1. 使用占位符的方式处理,订阅端接收到占位符就不对这一列进行处理,过程比较麻烦。
4. wal积压问题:复制槽记录的xmin是全局的,当发布端的表一直没更新时,xmin没有推进会导致wal积压。
解决方案:1. 创建一张心跳表,周期性写入数据并发布,推进xmin,防止wal堆积。
5. 大事务延迟:事务在commit之后才会进行解析,对于大事务来说会导致延迟。
6. 双向同步(PostgreSQL 16版本已经支持).
本文内容就到这啦,阅读完本篇,相信你也对PostgreSQL的逻辑复制实现原理和应用有了更深的理解了吧!我们下篇再见!





