29.4. 行过滤器 #
默认情况下,所有已发布表中的全部数据都会复制到相应的订阅端。可以使用行过滤器来减少被复制的数据。用户可能出于行为、安全或性能方面的原因选择使用行过滤器。如果某个已发布表设置了行过滤器,则只有当某一行的数据满足该行过滤器表达式时,该行才会被复制。这样就可以只复制一组表中的部分数据。行过滤器按表定义。对于每个需要过滤数据的已发布表,在表名后使用 WHERE 子句。WHERE 子句必须用括号括起来。详见 CREATE PUBLICATION。
29.4.1. 行过滤规则 #
行过滤器会在发布更改之前应用。如果行过滤器求值为 false 或 NULL,则该行不会被复制。WHERE 子句表达式会使用复制连接所用的同一角色来求值(即在 CREATE SUBSCRIPTION 的 CONNECTION
子句中指定的角色)。行过滤器对 TRUNCATE 命令没有影响。
29.4.2. 表达式限制 #
WHERE 子句只允许简单表达式。它不能包含用户定义的函数、操作符、类型和排序规则、系统列引用,或非不可变内置函数。
如果某个发布发布 UPDATE 或 DELETE 操作,则行过滤器的 WHERE 子句只能包含被复制标识覆盖的列(参见 REPLICA IDENTITY)。如果某个发布只发布
INSERT 操作,则行过滤器的 WHERE 子句可以使用任意列。
29.4.3. UPDATE 转换 #
每当处理 UPDATE 时,都会针对旧行和新行分别计算行过滤表达式(即分别使用更新前和更新后的数据)。如果两次求值都为 true,则复制该 UPDATE 更改。如果两次求值都为 false,则不复制该更改。如果旧行和新行中只有一个满足行过滤表达式,则会把 UPDATE 转换为 INSERT 或 DELETE,以避免任何数据不一致。订阅端上的行应当反映发布端行过滤表达式所定义的结果。
如果旧行满足行过滤表达式(即它已发送到订阅端),而新行不满足,那么从数据一致性的角度看,应当从订阅端移除旧行。因此 UPDATE 会被转换为 DELETE。
如果旧行不满足行过滤表达式(即它未发送到订阅端),而新行满足,那么从数据一致性的角度看,应当将新行加入订阅端。因此,UPDATE 会被转换为 INSERT。
表 29.1 总结了会应用哪些转换。
表 29.1. UPDATE 转换摘要
| 旧行 | 新行 | 转换 |
|---|---|---|
| 不匹配 | 不匹配 | 不复制 |
| 不匹配 | 匹配 | INSERT |
| 匹配 | 不匹配 | DELETE |
| 匹配 | 匹配 | UPDATE |
29.4.4. 分区表 #
如果发布包含分区表,则发布参数 publish_via_partition_root 确定使用哪个行过滤器。如果 publish_via_partition_root 为 true,则使用根分区表的行过滤器。否则,如果 publish_via_partition_root 为 false(默认值),则使用各个分区的行过滤器。
29.4.5. 初始数据同步 #
如果订阅需要复制预先存在的表数据,且某个发布包含 WHERE 子句,则只有满足行过滤表达式的数据才会被复制到订阅端。
如果某个订阅包含多个发布,而其中某张表带有不同的 WHERE 子句,那么满足这些表达式中任意一个的行都会被复制。详见第 29.4.6 节。
警告
由于初始数据同步在复制现有表数据时不会考虑 publish 参数,因此有些使用 DML 时本不会复制的行,也可能在此阶段被复制。参见第 29.10.1 节,并查看第 29.2.2 节中的示例。
注意
如果订阅端使用的是 15 之前的版本,那么即使发布中定义了行过滤器,复制预先存在的数据时也不会使用这些行过滤器。这是因为旧版本只能复制整张表的全部数据。
29.4.6. 组合多个行过滤器 #
如果某个订阅包含多个发布,而其中同一张表针对相同的 publish 操作使用了不同的行过滤器,那么这些表达式会按 OR 组合,因此满足任意一个表达式的行都会被复制。这意味着,如果出现以下任一情况,那么同一张表上的其他所有行过滤器都会变得多余:
其中某个发布没有行过滤器。
其中某个发布是使用
FOR ALL TABLES创建的。该子句不允许行过滤器。其中某个发布是使用
FOR TABLES IN SCHEMA创建的,并且该表属于所引用的模式。该子句不允许行过滤器。
29.4.7. 示例 #
创建一些表,供下面的示例使用。
/* pub # */ CREATE TABLE t1(a int, b int, c text, PRIMARY KEY(a,c)); /* pub # */ CREATE TABLE t2(d int, e int, f int, PRIMARY KEY(d)); /* pub # */ CREATE TABLE t3(g int, h int, i int, PRIMARY KEY(g));
创建一些发布。发布 p1 包含一个表(t1),该表带有行过滤器。发布 p2 包含两个表,其中 t1 没有行过滤器,而 t2 带有行过滤器。发布 p3 包含两个表,并且这两个表都带有行过滤器。
/* pub # */ CREATE PUBLICATION p1 FOR TABLE t1 WHERE (a > 5 AND c = 'NSW'); /* pub # */ CREATE PUBLICATION p2 FOR TABLE t1, t2 WHERE (e = 99); /* pub # */ CREATE PUBLICATION p3 FOR TABLE t2 WHERE (d = 10), t3 WHERE (g = 10);
psql 可用于显示每个发布的行过滤表达式(如果已定义)。
/* pub # */ \dRp+
Publication p1
Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------
postgres | f | f | t | t | t | t | none | f
Tables:
"public.t1" WHERE ((a > 5) AND (c = 'NSW'::text))
Publication p2
Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------
postgres | f | f | t | t | t | t | none | f
Tables:
"public.t1"
"public.t2" WHERE (e = 99)
Publication p3
Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------
postgres | f | f | t | t | t | t | none | f
Tables:
"public.t2" WHERE (d = 10)
"public.t3" WHERE (g = 10)
psql 可用于显示每个表的行过滤表达式(如果已定义)。可以看到,表 t1 属于两个发布,但只有在 p1 中带有行过滤器;表 t2 也属于两个发布,并且在这两个发布中具有不同的行过滤器。
/* pub # */ \d t1
Table "public.t1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | integer | | |
c | text | | not null |
Indexes:
"t1_pkey" PRIMARY KEY, btree (a, c)
Included in publications:
"p1" WHERE ((a > 5) AND (c = 'NSW'::text))
"p2"
/* pub # */ \d t2
Table "public.t2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
d | integer | | not null |
e | integer | | |
f | integer | | |
Indexes:
"t2_pkey" PRIMARY KEY, btree (d)
Included in publications:
"p2" WHERE (e = 99)
"p3" WHERE (d = 10)
/* pub # */ \d t3
Table "public.t3"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
g | integer | | not null |
h | integer | | |
i | integer | | |
Indexes:
"t3_pkey" PRIMARY KEY, btree (g)
Included in publications:
"p3" WHERE (g = 10)
在订阅端节点上,创建一个与发布端定义相同的表 t1,然后创建订阅 s1 来订阅发布 p1。
/* sub # */ CREATE TABLE t1(a int, b int, c text, PRIMARY KEY(a,c)); /* sub # */ CREATE SUBSCRIPTION s1 /* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=s1' /* sub - */ PUBLICATION p1;
插入一些行。只有满足发布 p1 中 t1 WHERE 子句的那些行会被复制。
/* pub # */ INSERT INTO t1 VALUES (2, 102, 'NSW'); /* pub # */ INSERT INTO t1 VALUES (3, 103, 'QLD'); /* pub # */ INSERT INTO t1 VALUES (4, 104, 'VIC'); /* pub # */ INSERT INTO t1 VALUES (5, 105, 'ACT'); /* pub # */ INSERT INTO t1 VALUES (6, 106, 'NSW'); /* pub # */ INSERT INTO t1 VALUES (7, 107, 'NT'); /* pub # */ INSERT INTO t1 VALUES (8, 108, 'QLD'); /* pub # */ INSERT INTO t1 VALUES (9, 109, 'NSW'); /* pub # */ SELECT * FROM t1; a | b | c ---+-----+----- 2 | 102 | NSW 3 | 103 | QLD 4 | 104 | VIC 5 | 105 | ACT 6 | 106 | NSW 7 | 107 | NT 8 | 108 | QLD 9 | 109 | NSW (8 rows)
/* sub # */ SELECT * FROM t1; a | b | c ---+-----+----- 6 | 106 | NSW 9 | 109 | NSW (2 rows)
更新一些数据,此时旧行和新行的值都满足发布 p1 中 t1 WHERE 子句。该 UPDATE 会像平常一样复制该更改。
/* pub # */ UPDATE t1 SET b = 999 WHERE a = 6; /* pub # */ SELECT * FROM t1; a | b | c ---+-----+----- 2 | 102 | NSW 3 | 103 | QLD 4 | 104 | VIC 5 | 105 | ACT 7 | 107 | NT 8 | 108 | QLD 9 | 109 | NSW 6 | 999 | NSW (8 rows)
/* sub # */ SELECT * FROM t1; a | b | c ---+-----+----- 9 | 109 | NSW 6 | 999 | NSW (2 rows)
更新一些数据,此时旧行的值不满足发布 p1 中 t1 WHERE 子句,而新行的值满足该子句。UPDATE 会被转换为 INSERT,并且该更改会被复制。可以在订阅端看到新行。
/* pub # */ UPDATE t1 SET a = 555 WHERE a = 2; /* pub # */ SELECT * FROM t1; a | b | c -----+-----+----- 3 | 103 | QLD 4 | 104 | VIC 5 | 105 | ACT 7 | 107 | NT 8 | 108 | QLD 9 | 109 | NSW 6 | 999 | NSW 555 | 102 | NSW (8 rows)
/* sub # */ SELECT * FROM t1; a | b | c -----+-----+----- 9 | 109 | NSW 6 | 999 | NSW 555 | 102 | NSW (3 rows)
更新一些数据,此时旧行的值满足发布 p1 中 t1 WHERE 子句,而新行的值不再满足该子句。UPDATE 会被转换为 DELETE,并且该更改会被复制。可以看到该行已从订阅端移除。
/* pub # */ UPDATE t1 SET c = 'VIC' WHERE a = 9; /* pub # */ SELECT * FROM t1; a | b | c -----+-----+----- 3 | 103 | QLD 4 | 104 | VIC 5 | 105 | ACT 7 | 107 | NT 8 | 108 | QLD 6 | 999 | NSW 555 | 102 | NSW 9 | 109 | VIC (8 rows)
/* sub # */ SELECT * FROM t1; a | b | c -----+-----+----- 6 | 999 | NSW 555 | 102 | NSW (2 rows)
下面的示例展示了发布参数 publish_via_partition_root
在分区表场景下如何决定使用父表还是子表的行过滤器。
在发布端创建一个分区表。
/* pub # */ CREATE TABLE parent(a int PRIMARY KEY) PARTITION BY RANGE(a); /* pub # */ CREATE TABLE child PARTITION OF parent DEFAULT;
在订阅端创建相同的表。
/* sub # */ CREATE TABLE parent(a int PRIMARY KEY) PARTITION BY RANGE(a); /* sub # */ CREATE TABLE child PARTITION OF parent DEFAULT;
创建一个发布 p4,然后订阅它。发布参数 publish_via_partition_root 设置为 true。分区表(parent)和分区(child)上都定义了行过滤器。
/* pub # */ CREATE PUBLICATION p4 FOR TABLE parent WHERE (a < 5), child WHERE (a >= 5) /* pub - */ WITH (publish_via_partition_root=true);
/* sub # */ CREATE SUBSCRIPTION s4 /* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=s4' /* sub - */ PUBLICATION p4;
直接向 parent 和 child 表插入一些值。它们会使用 parent 的行过滤器进行复制(因为 publish_via_partition_root 为 true)。
/* pub # */ INSERT INTO parent VALUES (2), (4), (6); /* pub # */ INSERT INTO child VALUES (3), (5), (7); /* pub # */ SELECT * FROM parent ORDER BY a; a --- 2 3 4 5 6 7 (6 rows)
/* sub # */ SELECT * FROM parent ORDER BY a; a --- 2 3 4 (3 rows)
重复相同的测试,但将 publish_via_partition_root 设为不同的值。这里发布参数 publish_via_partition_root 设置为 false,并且在分区(child)上定义了行过滤器。
/* pub # */ DROP PUBLICATION p4; /* pub # */ CREATE PUBLICATION p4 FOR TABLE parent, child WHERE (a >= 5) /* pub - */ WITH (publish_via_partition_root=false);
/* sub # */ ALTER SUBSCRIPTION s4 REFRESH PUBLICATION;
像前面一样在发布端执行插入。它们会使用 child 的行过滤器进行复制(因为 publish_via_partition_root 为 false)。
/* pub # */ TRUNCATE parent; /* pub # */ INSERT INTO parent VALUES (2), (4), (6); /* pub # */ INSERT INTO child VALUES (3), (5), (7); /* pub # */ SELECT * FROM parent ORDER BY a; a --- 2 3 4 5 6 7 (6 rows)
/* sub # */ SELECT * FROM child ORDER BY a; a --- 5 6 7 (3 rows)