CREATE SUBSCRIPTION
CREATE SUBSCRIPTION — 定义一个新的订阅
大纲
CREATE SUBSCRIPTIONsubscription_name{ SERVERserver_name| CONNECTION 'conninfo' } PUBLICATIONpublication_name[, ...] [ WITH (subscription_parameter[=value] [, ... ] ) ]
描述
CREATE SUBSCRIPTION 添加一个新的逻辑复制订阅。创建订阅的用户将成为该订阅的所有者。订阅名称必须与当前数据库中任何现有订阅的名称不同。
订阅表示与发布者的复制连接。因此,除了在本地系统目录中添加定义之外,该命令通常还会在发布者上创建一个复制槽。
除非订阅初始即被禁用,否则在执行该命令所在事务提交时,会启动一个逻辑复制工作进程为新订阅复制数据。
要能够创建订阅,必须具有 pg_create_subscription 角色的权限,以及当前数据库上的 CREATE 权限。
参数
subscription_name#新订阅的名称。
SERVER#server_name供连接使用的外部服务器。该服务器的外部数据包装器必须已注册
connection_function,并且该服务器上必须存在订阅所有者的用户映射。此外,订阅所有者必须在server_name上具有USAGE权限。CONNECTION '#conninfo'定义如何连接到发布者数据库的 libpq 连接字符串。详情请参见第 32.1.1 节。
PUBLICATION#publication_name[, ...]要订阅的发布者上的发布名称。
WITH (#subscription_parameter[=value] [, ... ] )该子句为订阅指定可选参数。
下列参数控制订阅创建期间的行为:
connect(boolean) #指定
CREATE SUBSCRIPTION命令是否连接到发布者。默认值为true。将其设为false会强制把create_slot、enabled和copy_data的值设为false。(不能将connect设为false的同时,再把create_slot、enabled或copy_data设为true。)由于该选项为
false时不会建立连接,因此不会订阅任何表和序列。要启动复制,你必须手工创建复制槽、在需要时启用故障切换、启用订阅,并刷新订阅。示例见第 29.2.3 节。create_slot(boolean) #指定该命令是否应在发布者上创建复制槽。默认值为
true。如果设为
false,则必须以其他方式自行创建发布者上的复制槽。示例见第 29.2.3 节。enabled(boolean) #指定订阅是应当主动进行复制,还是仅完成设置但暂不启动。默认值为
true。slot_name(string) #要使用的发布者上的复制槽名称。默认使用订阅名称作为槽名。该名称不能为
pg_conflict_detection,因为它保留给冲突检测使用。将
slot_name设为NONE表示该订阅不关联任何复制槽。这类订阅还必须同时将enabled和create_slot设为false。当你打算稍后手工创建复制槽时,可使用此设置。示例见第 29.2.3 节。将
slot_name设为有效名称且create_slot设为false时,所指定复制槽的failover属性值可能与订阅中对应的failover参数不同。应始终确保该槽的failover属性与订阅中的对应参数一致,反之亦然。否则,发布者上的该槽的行为可能与这些订阅选项所表明的不同:例如,即使订阅的failover选项已禁用,发布者上的该槽仍可能被同步到备库;或者即使订阅的failover选项已启用,该槽也可能不会被同步到备库。
下列参数控制订阅创建后的复制行为:
binary(boolean) #指定订阅是否请求发布者以二进制格式(而不是文本格式)发送数据。默认值为
false。任何初始表同步复制(见copy_data)也使用相同格式。二进制格式可能比文本格式更快,但在不同机器架构和 PostgreSQL 版本之间的可移植性较差。二进制格式对数据类型非常敏感;例如,虽然在文本格式下可以正常工作,但它不允许从smallint列复制到integer列。即使启用了此选项,也只有具有二进制发送和接收函数的数据类型才会以二进制方式传输。注意,初始同步要求所有数据类型都具有二进制发送和接收函数,否则同步将失败(关于发送/接收函数的更多信息,请参见 CREATE TYPE)。此参数对序列没有影响。在进行跨版本复制时,可能会出现这样的情况:发布者对某种数据类型有二进制发送函数,但订阅者缺少该类型的二进制接收函数。在这种情况下,数据传输会失败,因此不能使用
binary选项。如果发布者使用的是 16 之前版本的 PostgreSQL,那么即使
binary = true,任何初始表同步也都会使用文本格式。copy_data(boolean) #指定复制开始时是否复制所订阅发布中的预先存在的数据。默认值为
true。如果这些发布包含
WHERE子句,将会影响被复制的数据。详情请参见注意。关于
copy_data = true如何与origin参数相互作用的细节,请参见注意。设置
copy_data = true时,可能出现关于发布者与订阅者之间序列定义差异的警告。处理这些警告的建议见第 29.7.1 节。streaming(enum) #指定是否为该订阅启用进行中事务的流式传输。默认值为
parallel,表示如果有可用的并行应用工作进程,接收到的更改会直接通过其中之一应用。如果没有空闲的并行应用工作进程可以处理流式事务,那么这些更改会写入临时文件,并在事务提交后再应用。注意,如果并行应用工作进程中发生错误,远端事务的完成 LSN 可能不会记录到服务器日志中。此参数对序列没有影响。小心
当发布者与订阅者的模式不同时,存在发生死锁的风险,尽管这种情况很少见。应用工作进程会自动重试这些事务。
如果设为
on,接收到的更改会写入临时文件,然后仅在事务在发布者上提交且被订阅者接收之后才应用。如果设为
off,所有事务都会先在发布者上完全解码,然后才整体发送给订阅者。synchronous_commit(enum) #该参数的值会覆盖此订阅应用工作进程中的 synchronous_commit 设置。默认值为
off。此参数对序列没有影响。对逻辑复制来说,使用
off是安全的:如果订阅者因缺少同步而丢失了事务,数据会再次从发布者发送过来。在进行同步逻辑复制时,可能更适合使用不同的设置。逻辑复制工作进程会向发布者报告写入和刷盘位置,而在使用同步复制时,发布者会等待真正的刷盘完成。这意味着,当订阅被用于同步复制时,将订阅者的
synchronous_commit设为off可能会增加发布者上COMMIT的延迟。在这种场景下,将synchronous_commit设为local或更高可能更有利。two_phase(boolean) #指定是否为该订阅启用两阶段提交。默认值为
false。此参数对序列没有影响。启用两阶段提交时,预备事务会在
PREPARE TRANSACTION时发送给订阅者,并且在订阅者上也会作为两阶段事务处理。否则,预备事务只有在提交时才会发送给订阅者,随后由订阅者立即处理。两阶段提交的实现要求复制已经成功完成初始表同步阶段。因此,即使订阅启用了
two_phase,内部的两阶段状态也会暂时保持为“pending”,直到初始化阶段完成。要了解实际的两阶段状态,请参见pg_subscription的subtwophasestate列。disable_on_error(boolean) #指定如果订阅工作进程在从发布者复制数据期间检测到任何错误,是否自动禁用该订阅。默认值为
false。password_required(boolean) #如果设为
true,则由于该订阅而建立的到发布者的连接必须使用密码认证,并且密码必须作为连接字符串的一部分指定。如果该订阅由超级用户拥有,则此设置会被忽略。默认值为true。只有超级用户才能将该值设为false。run_as_owner(boolean) #如果为
true,所有复制操作都以订阅所有者的身份执行。如果为false,复制工作进程会在每张表或序列上以该关系所有者的身份执行操作。后一种配置通常安全得多;详情请参见第 29.12 节。默认值为false。origin(string) #指定订阅是请求发布者仅发送没有复制源的更改,还是无论复制源如何都发送更改。将
origin设为none表示订阅会请求发布者仅发送没有复制源的更改。将origin设为any表示发布者无论复制源如何都发送更改。默认值为any。此参数对序列没有影响。关于
copy_data = true如何与origin参数相互作用的细节,请参见注意。failover(boolean) #指定与该订阅关联的复制槽是否启用同步到备库,以便在故障切换后能够从新的主库继续逻辑复制。默认值为
false。retain_dead_tuples(boolean) #指定是否保留订阅者上冲突检测所需的信息(例如死元组、提交时间戳和来源)。默认值为
false。如果设为true,则会启用 update_deleted 的检测,并在订阅者上创建一个名为“pg_conflict_detection”的物理复制槽,以防止用于检测冲突的信息被移除。此参数对序列没有影响。请注意,只有在创建该槽之后,才会保留用于冲突检测的信息。可以通过查询 pg_replication_slots 来验证该槽是否存在。即使同一节点上的多个订阅启用此选项,也只会创建一个复制槽。此外,必须将
wal_level设置为replica或更高,才能允许使用该复制槽。小心
请注意,如果订阅被禁用,用于冲突检测的信息将无法被清除;因此,这些信息会一直累积,直到订阅被启用。为了防止过度累积,建议在订阅将长时间处于非活动状态时禁用
retain_dead_tuples。如果订阅已启用,但其应用工作进程没有运行(例如工作进程反复无法应用某项更改),情况也相同。可以通过 pg_replication_slots 中“pg_conflict_detection”复制槽的xmin查看所保留的最旧信息。此外,在逻辑复制中为冲突检测启用
retain_dead_tuples时,设计复制拓扑以平衡数据保留需求与整体系统性能非常重要。该选项在适当使用时只会带来极小的性能开销。以下场景展示了启用该选项时的有效用法模式。a. 双向写入的大表:对于在发布者和订阅者节点上都存在并发写入的大表,发布者可以在创建发布时定义行过滤器来分割数据。这样可以让多个订阅并行复制该表的互斥子集,从而优化吞吐量。
b. 可写入的订阅者:如果预期订阅者节点需要执行写操作,则可以通过多个发布和订阅来组织复制。通过将表分配到这些发布中,工作负载会分散到多个应用工作进程之间,从而提高并发性并减少争用。
c. 只读订阅者:在涉及一个或多个发布者节点执行并发写操作的配置中,只读订阅者节点如果执行的是索引扫描,则可能不会看到性能影响。然而,如果订阅者由于复制延迟或扫描性能(例如顺序扫描)而受到影响,则需要采用前述两种策略之一来在订阅者上分散负载。
如果发布者是物理备库,则不能启用此选项。
启用此选项只能保证保留用于冲突检测的信息,且仅针对在发布者本地发生的更改。对于来自不同来源的更改,无法保证可靠的冲突检测。
检测 update_deleted 还要求在订阅者上启用
track_commit_timestamp。如果它未启用,而启用了retain_dead_tuples,则会发出警告。如果之后禁用了track_commit_timestamp,用于冲突检测的信息仍会继续保留,但冲突会改为报告为 update_missing,因此保留这些信息便失去了作用。max_retention_duration(integer) #当启用
retain_dead_tuples时,此订阅的应用工作进程允许保留用于冲突检测的信息的最长时长,单位为毫秒。默认值为0,表示在不再需要进行检测之前一直保留这些信息。如果所有与启用了
retain_dead_tuples的订阅相关联的应用工作进程都确认保留时长已经超过相应订阅中设置的max_retention_duration,则用于冲突检测的信息将不再被保留。只要至少有一个应用工作进程确认保留时长仍在指定限制内,或者创建了一个新的retain_dead_tuples = true订阅,保留就会自动恢复。或者,也可以通过重新启用retain_dead_tuples手工恢复保留。注意,如果其他订阅为此参数设置了大于 0 的值且尚未超过该值,或者将此选项设为
0,则整体保留不会停止。只有当启用了
retain_dead_tuples且与该订阅关联的应用工作进程处于活动状态时,此选项才有效。小心
注意,订阅被禁用或其应用工作进程未运行时,不会评估保留时长。因此,无论此设置如何,用于冲突检测的信息都会继续累积,直到订阅被启用或其应用工作进程恢复运行。为防止过度累积,如果订阅将长时间处于非活动状态,应考虑禁用
retain_dead_tuples。警告
注意,将此选项设为非零值可能导致用于冲突检测的信息被过早移除,从而可能造成错误的冲突检测。
wal_receiver_timeout(text) #该参数的值会覆盖此订阅应用工作进程中的 wal_receiver_timeout 设置。默认值为
-1,表示不覆盖全局设置,也就是说将改用服务器配置、命令行、角色或数据库设置中的值。
当指定 boolean 类型的参数时,可以省略
= value
部分,这等价于指定 TRUE。
注解
关于如何在订阅与发布实例之间配置访问控制,详见第 29.12 节。
当创建复制槽时(这是默认行为),CREATE
SUBSCRIPTION 不能在事务块内部执行。
创建一个连接到同一数据库集簇的订阅(例如在同一集簇中的不同数据库之间复制,或者在同一数据库内复制)时,只有在复制槽不作为同一命令的一部分创建的情况下才会成功。否则,CREATE SUBSCRIPTION
调用将挂起。要实现这种用法,应分别创建复制槽(使用函数
pg_create_logical_replication_slot 并指定插件名
pgoutput),然后使用参数
create_slot = false 创建订阅。示例见第 29.2.3 节。这是一个实现限制,未来版本中可能会解除。
如果发布中的任何表带有 WHERE 子句,则对
expression 求值为
false 或 NULL 的行不会被发布。如果订阅包含多个发布,且同一张表在这些发布中使用了不同的
WHERE 子句,那么只要任意一个表达式(针对相应的发布操作)满足,该行就会被发布。对于不同的
WHERE 子句,如果其中某个发布没有
WHERE 子句(针对相应的发布操作),或者该发布被声明为
FOR ALL TABLES
或
FOR TABLES IN SCHEMA,则无论其他表达式如何定义,该行都会被发布。如果订阅者使用的是 15 之前版本的 PostgreSQL,那么在初始数据同步阶段会忽略所有行过滤。在这种情况下,用户可能需要考虑删除那些初始复制过来、但与后续过滤不兼容的数据。由于初始数据同步在复制现有表数据时不会考虑发布的
publish
参数,因此有些使用 DML 时本不会复制的行,也可能在此阶段被复制。示例见第 29.2.2 节。
如果订阅包含多个发布,而其中同一张表使用了不同的列列表进行发布,则不受支持。
允许指定不存在的发布,以便用户稍后再创建这些发布。这意味着
pg_subscription
中可能包含不存在的发布。
当使用 copy_data = true 和
origin = NONE 这一订阅参数组合时,初始同步的表数据会直接从发布者复制过来,因此无法得知这些数据的真实来源。如果发布者本身也有订阅,那么复制过来的表数据可能来自更上游。系统会检测到这种情况,并向用户发出一条 WARNING,但这条警告只表明可能存在问题;用户仍需自行进行必要检查,以确认复制过来的数据来源是否确实符合预期。
若要找出哪些表可能包含非本地来源的数据(由于在发布者上创建了其他订阅),可以尝试执行以下 SQL 查询:
# substitute <pub-names> below with your publication name(s) to be queried
SELECT DISTINCT PT.schemaname, PT.tablename
FROM pg_publication_tables PT
JOIN pg_class C ON (C.relname = PT.tablename)
JOIN pg_namespace N ON (N.nspname = PT.schemaname),
pg_subscription_rel PS
WHERE C.relnamespace = N.oid AND
(PS.srrelid = C.oid OR
C.oid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION
SELECT relid FROM pg_partition_tree(PS.srrelid))) AND
PT.pubname IN (<pub-names>);
示例
创建一个指向远端服务器的订阅,复制发布
mypublication 和 insert_only 中的表,并在提交时立即开始复制:
CREATE SUBSCRIPTION mysub
CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb'
PUBLICATION mypublication, insert_only;
创建一个指向远端服务器的订阅,复制 insert_only 发布中的表,并且要等到稍后启用时才开始复制。
CREATE SUBSCRIPTION mysub
CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb'
PUBLICATION insert_only
WITH (enabled = false);
兼容性
CREATE SUBSCRIPTION 是
PostgreSQL 扩展。