逻辑复制
逻辑复制(Logical Replication),是一种根据数据对象的 复制标识(Replica Identity)(通常是主键)复制数据对象及其变化的方法。
逻辑复制 这个术语与 物理复制相对应,物理复制使用精确的块地址与逐字节复制,而逻辑复制则允许对复制过程进行精细的控制。
逻辑复制基于 发布(Publication) 与 订阅(Subscription)模型:
- 一个 发布者(Publisher) 上可以有多个发布,一个 订阅者(Subscriber) 上可以有多个 订阅 。
- 一个发布可被多个订阅者订阅,一个订阅只能订阅一个发布者,但可订阅同发布者上的多个不同发布。
针对一张表的逻辑复制通常是这样的:订阅者获取发布者数据库上的一个快照,并拷贝表中的存量数据。一旦完成数据拷贝,发布者上的变更(增删改清)就会实时发送到订阅者上。订阅者会按照相同的顺序应用这些变更,因此可以保证逻辑复制的事务一致性。这种方式有时候又称为 事务性复制(transactional replication)。
逻辑复制的典型用途是:
- 迁移,跨PostgreSQL大版本,跨操作系统平台进行复制。
- CDC,收集数据库(或数据库的一个子集)中的增量变更,在订阅者上为增量变更触发触发器执行定制逻辑。
- 分拆,将多个数据库集成为一个,或者将一个数据库拆分为多个,进行精细的分拆集成与访问控制。
逻辑订阅者的行为就是一个普通的PostgreSQL实例(主库),逻辑订阅者也可以创建自己的发布,拥有自己的订阅者。
如果逻辑订阅者只读,那么不会有冲突。如果会写入逻辑订阅者的订阅集,那么就可能会出现冲突。
发布(Publication)
一个 发布(Publication) 可以在物理复制主库 上定义。创建发布的节点被称为 发布者(Publisher) 。
一个 发布 是 由一组表构成的变更集合。也可以被视作一个 变更集(change set) 或 复制集(Replication Set) 。每个发布都只能在一个 数据库(Database) 中存在。
发布不同于模式(Schema),不会影响表的访问方式。(表纳不纳入发布,自身访问不受影响)
发布目前只能包含表(即:索引,序列号,物化视图这些不会被发布),每个表可以添加到多个发布中。
除非针对ALL TABLES
创建发布,否则发布中的对象(表)只能(通过ALTER PUBLICATION ADD TABLE
)被显式添加。
发布可以筛选所需的变更类型:包括INSERT
、UPDATE
、DELETE
和TRUNCATE
的任意组合,类似触发器事件,默认所有变更都会被发布。
复制标识
一个被纳入发布中的表,必须带有 复制标识(Replica Identity),只有这样才可以在订阅者一侧定位到需要更新的行,完成UPDATE
与DELETE
操作的复制。
默认情况下,主键 (Primary Key)是表的复制标识,非空列上的唯一索引 (UNIQUE NOT NULL)也可以用作复制标识。
如果没有任何复制标识,可以将复制标识设置为FULL
,也就是把整个行当作复制标识。(一种有趣的情况,表中存在多条完全相同的记录,也可以被正确处理,见后续案例)使用FULL
模式的复制标识效率很低(因为每一行修改都需要在订阅者上执行全表扫描,很容易把订阅者拖垮),所以这种配置只能是保底方案。使用FULL
模式的复制标识还有一个限制,订阅端的表上的复制身份所包含的列,要么与发布者一致,要么比发布者更少。
INSERT
操作总是可以无视 复制标识 直接进行(因为插入一条新记录,在订阅者上并不需要定位任何现有记录;而删除和更新则需要通过复制标识 定位到需要操作的记录)。如果一个没有 复制标识 的表被加入到带有UPDATE
和DELETE
的发布中,后续的UPDATE
和DELETE
会导致发布者上报错。
表的复制标识模式可以查阅pg_class.relreplident
获取,可以通过ALTER TABLE
进行修改。
ALTER TABLE tbl REPLICA IDENTITY
{ DEFAULT | USING INDEX index_name | FULL | NOTHING };
尽管各种排列组合都是可能的,然而在实际使用中,只有三种可行的情况。
- 表上有主键,使用默认的
default
复制标识 - 表上没有主键,但是有非空唯一索引,显式配置
index
复制标识 - 表上既没有主键,也没有非空唯一索引,显式配置
full
复制标识(运行效率非常低,仅能作为兜底方案) - 其他所有情况,都无法正常完成逻辑复制功能。输出的信息不足,可能会报错,也可能不会。
- 特别需要注意:如果
nothing
复制标识的表纳入到逻辑复制中,对其进行删改会导致发布端报错!
管理发布
CREATE PUBLICATION
用于创建发布,DROP PUBLICATION
用于移除发布,ALTER PUBLICATION
用于修改发布。
发布创建之后,可以通过ALTER PUBLICATION
动态地向发布中添加或移除表,这些操作都是事务性的。
CREATE PUBLICATION name
[ FOR TABLE [ ONLY ] table_name [ * ] [, ...]
| FOR ALL TABLES ]
[ WITH ( publication_parameter [= value] [, ... ] ) ]
ALTER PUBLICATION name ADD TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name SET TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name DROP TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name SET ( publication_parameter [= value] [, ... ] )
ALTER PUBLICATION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION name RENAME TO new_name
DROP PUBLICATION [ IF EXISTS ] name [, ...];
publication_parameter
主要包括两个选项:
publish
:定义要发布的变更操作类型,逗号分隔的字符串,默认为insert, update, delete, truncate
。publish_via_partition_root
:13后的新选项,如果为真,分区表将使用根分区的复制标识进行逻辑复制。
订阅
订阅(Subscription) 是逻辑复制的下游。定义订阅的节点被称为 订阅者(Subscriber) 。
订阅定义了:如何连接到另一个数据库,以及需要订阅目标发布者上的哪些发布。
逻辑订阅者的行为与一个普通的PostgreSQL实例(主库)无异,逻辑订阅者也可以创建自己的发布,拥有自己的订阅者。
每个订阅者,都会通过一个 复制槽(Replication) 来接收变更,在初始数据复制阶段,可能会需要更多的临时复制槽。
逻辑复制订阅可以作为同步复制的备库,备库的名字默认就是订阅的名字,也可以通过在连接信息中设置application_name
来使用别的名字。
只有超级用户才可以用pg_dump
转储订阅的定义,因为只有超级用户才可以访问pg_subscription
视图,普通用户尝试转储时会跳过并打印警告信息。
逻辑复制不会复制DDL变更,因此发布集中的表必须已经存在于订阅端上。只有普通表上的变更会被复制,视图、物化视图、序列号,索引这些都不会被复制。
发布与订阅端的表是通过完整限定名(如public.table
)进行匹配的,不支持把变更复制到一个名称不同的表上。
发布与订阅端的表的列也是通过名称匹配的。列的顺序无关紧要,数据类型也不一定非得一致,只要两个列的文本表示兼容即可,即数据的文本表示可以转换为目标列的类型。订阅端的表可以包含有发布端没有的列,这些新列都会使用默认值填充。
管理订阅
CREATE SUBSCRIPTION
用于创建订阅,DROP SUBSCRIPTION
用于移除订阅,ALTER SUBSCRIPTION
用于修改订阅。
订阅创建之后,可以通过ALTER SUBSCRIPTION
随时暂停与恢复订阅。
移除并重建订阅会导致同步信息丢失,这意味着相关数据需要重新进行同步。
CREATE SUBSCRIPTION subscription_name
CONNECTION 'conninfo'
PUBLICATION publication_name [, ...]
[ WITH ( subscription_parameter [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name CONNECTION 'conninfo'
ALTER SUBSCRIPTION name SET PUBLICATION publication_name [, ...] [ WITH ( set_publication_option [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name REFRESH PUBLICATION [ WITH ( refresh_option [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name ENABLE
ALTER SUBSCRIPTION name DISABLE
ALTER SUBSCRIPTION name SET ( subscription_parameter [= value] [, ... ] )
ALTER SUBSCRIPTION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
ALTER SUBSCRIPTION name RENAME TO new_name
DROP SUBSCRIPTION [ IF EXISTS ] name;
subscription_parameter
定义了订阅的一些选项,包括:
copy_data(bool)
:复制开始后,是否拷贝数据,默认为真create_slot(bool)
:是否在发布者上创建复制槽,默认为真enabled(bool)
:是否启用该订阅,默认为真connect(bool)
:是否尝试连接到发布者,默认为真,置为假会把上面几个选项强制设置为假。synchronous_commit(bool)
:是否启用同步提交,向主库上报自己的进度信息。slot_name
:订阅所关联的复制槽名称,设置为空会取消订阅与复制槽的关联。