Skip to content

Commit ed23d32

Browse files
authored
Merge pull request #245 from hs-liuxh/v1.17
add IvorySQL experimental feature: Default Logical Replication Support for Tables Without Primary Key
2 parents b28a372 + 1c4537f commit ed23d32

File tree

4 files changed

+264
-2
lines changed

4 files changed

+264
-2
lines changed

CN/modules/ROOT/nav.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
** xref:v1.17/17.adoc[7、兼容Oracle函数与存储过程]
3030
** xref:v1.17/18.adoc[8、内置数据类型与内置函数]
3131
** xref:v1.17/19.adoc[9、新增Oracle兼容模式的端口与IP]
32-
** xref:v1.17/41.adoc[10、全局唯一索引]
32+
* IvorySQL试验田
33+
** xref:v1.17/41.adoc[1、全局唯一索引]
34+
** xref:v1.17/42.adoc[2、新增无主键表默认支持逻辑复制]
3335
* xref:v1.17/20.adoc[社区贡献指南]
3436
* xref:v1.17/21.adoc[工具参考]
3537
* xref:v1.17/22.adoc[FAQ]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= 新增无主键表默认支持逻辑复制
7+
8+
== 功能介绍
9+
10+
在 PostgreSQL/IvorySQL 的逻辑复制中,UPDATE 和 DELETE 操作需要依赖复制标识(Replica Identity)来定位订阅端的目标行。默认情况下,表使用 `REPLICA IDENTITY DEFAULT`,此时系统依赖主键(Primary Key)来定位行。如果表没有主键,复制策略将回退为 `REPLICA IDENTITY NOTHING`,此时 UPDATE 和 DELETE 操作将因无法定位行而报错。
11+
12+
IvorySQL 新增了 GUC 参数 `logical_replication_fallback_to_full_identity`。当该参数开启后,如果表的复制标识为 `DEFAULT` 且没有主键,系统会自动将其回退为 `REPLICA IDENTITY FULL`——即在 WAL 中记录完整的旧行数据,使无主键表的 UPDATE 和 DELETE 操作能够通过逻辑复制正常执行。
13+
14+
该功能仅在发布端(Publisher)生效,订阅端无需任何额外配置。
15+
16+
== 参数说明
17+
18+
[source,sql]
19+
----
20+
# postgresql.conf
21+
logical_replication_fallback_to_full_identity = on
22+
----
23+
24+
参数说明:
25+
26+
- 类型:`boolean`;
27+
- 默认值:`off`;
28+
- 作用域:`sighup`,可通过修改 `postgresql.conf` 后执行 `SELECT pg_reload_conf();` 使配置生效,无需重启数据库;
29+
- 适用节点:仅在发布端(Publisher)生效,订阅端无需配置。
30+
31+
== 测试用例
32+
33+
=== 未开启参数时,无主键表的 UPDATE 和 DELETE 复制失败
34+
35+
[source,sql]
36+
----
37+
-- 发布端:创建无主键表
38+
CREATE TABLE test_no_pk (id int, name text);
39+
40+
-- 订阅端:创建相同的表结构
41+
CREATE TABLE test_no_pk (id int, name text);
42+
43+
-- 发布端:创建发布并添加表
44+
CREATE PUBLICATION tap_pub FOR TABLE test_no_pk;
45+
46+
-- 订阅端:创建订阅
47+
CREATE SUBSCRIPTION tap_sub CONNECTION 'host=publisher dbname=postgres' PUBLICATION tap_pub;
48+
49+
-- 发布端:INSERT 操作始终正常(不依赖复制标识)
50+
INSERT INTO test_no_pk VALUES (1, 'alice');
51+
52+
-- 发布端:UPDATE 操作失败(无主键,无法定位目标行)
53+
UPDATE test_no_pk SET name = 'bob' WHERE id = 1;
54+
-- ERROR: cannot update table "test_no_pk" because it does not have a replica identity and publishes updates
55+
56+
-- 发布端:DELETE 操作同样失败
57+
DELETE FROM test_no_pk WHERE id = 1;
58+
-- ERROR: cannot delete from table "test_no_pk" because it does not have a replica identity and publishes deletes
59+
----
60+
61+
=== 开启参数后,无主键表的 UPDATE 和 DELETE 复制正常
62+
63+
[source,sql]
64+
----
65+
-- 发布端:开启参数并重新加载配置
66+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = on;
67+
SELECT pg_reload_conf();
68+
69+
-- 发布端:INSERT 正常
70+
INSERT INTO test_no_pk VALUES (1, 'alice');
71+
72+
-- 发布端:UPDATE 正常(自动按 FULL 方式记录完整旧行数据)
73+
UPDATE test_no_pk SET name = 'bob' WHERE id = 1;
74+
75+
-- 发布端:DELETE 正常
76+
DELETE FROM test_no_pk WHERE id = 1;
77+
----
78+
79+
=== 参数不影响有主键的表
80+
81+
[source,sql]
82+
----
83+
-- 有主键的表始终使用主键定位行,不受该参数影响
84+
CREATE TABLE test_with_pk (id int PRIMARY KEY, name text);
85+
86+
-- 无论参数是否开启,UPDATE 和 DELETE 均正常工作
87+
INSERT INTO test_with_pk VALUES (1, 'alice');
88+
UPDATE test_with_pk SET name = 'bob' WHERE id = 1;
89+
DELETE FROM test_with_pk WHERE id = 1;
90+
----
91+
92+
=== 参数不影响显式设置为 REPLICA IDENTITY NOTHING 的表
93+
94+
[source,sql]
95+
----
96+
-- 创建表并显式设置为 REPLICA IDENTITY NOTHING
97+
CREATE TABLE test_nothing (id int, data text);
98+
ALTER TABLE test_nothing REPLICA IDENTITY NOTHING;
99+
100+
-- 即使开启了 logical_replication_fallback_to_full_identity,
101+
-- UPDATE 和 DELETE 仍然失败(参数不会覆盖显式的 NOTHING 设置)
102+
INSERT INTO test_nothing VALUES (1, 'test');
103+
UPDATE test_nothing SET data = 'modified' WHERE id = 1;
104+
-- ERROR: cannot update table "test_nothing" because it does not have a replica identity and publishes updates
105+
106+
DELETE FROM test_nothing WHERE id = 1;
107+
-- ERROR: cannot delete from table "test_nothing" because it does not have a replica identity and publishes deletes
108+
----
109+
110+
=== 运行时动态切换参数
111+
112+
[source,sql]
113+
----
114+
-- 关闭参数:无主键表的 UPDATE/DELETE 将恢复为报错行为
115+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = off;
116+
SELECT pg_reload_conf();
117+
118+
-- 开启参数:无主键表的 UPDATE/DELETE 将正常工作
119+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = on;
120+
SELECT pg_reload_conf();
121+
----
122+
123+
== 功能限制
124+
125+
. 该参数仅对复制标识为 `REPLICA IDENTITY DEFAULT` 且没有主键的表生效;已显式设置为 `FULL`、`USING INDEX` 或 `NOTHING` 的表不受影响;
126+
. 开启该参数后,无主键表的 UPDATE 和 DELETE 会在 WAL 中记录完整的旧行数据(与 `REPLICA IDENTITY FULL` 效果相同),相比有主键时只记录主键列,会增加 WAL 体积和网络传输量;
127+
. 该参数仅在发布端生效,订阅端无需配置;
128+
. INSERT 操作不依赖复制标识,无论该参数是否开启均可正常复制;
129+
. 该参数不替代显式的 `ALTER TABLE ... REPLICA IDENTITY FULL` 设置,也不会覆盖显式设置为 `NOTHING` 的表。

EN/modules/ROOT/nav.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
** xref:v1.17/17.adoc[7、Compatible with Oracle functions and stored procedures]
3030
** xref:v1.17/18.adoc[8、Built-in data types and built-in functions]
3131
** xref:v1.17/19.adoc[9、Added Oracle compatibility mode ports and IP]
32-
** xref:v1.17/41.adoc[10、Global Unique Index]
32+
* IvorySQL Experimental Features
33+
** xref:v1.17/41.adoc[1、Global Unique Index]
34+
** xref:v1.17/42.adoc[2、Default Logical Replication Support for Tables Without Primary Key]
3335
* xref:v1.17/20.adoc[Community contribution]
3436
* xref:v1.17/21.adoc[Tool Reference]
3537
* xref:v1.17/22.adoc[FAQ]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= Default Logical Replication Support for Tables Without Primary Key
7+
8+
== Overview
9+
10+
In PostgreSQL/IvorySQL logical replication, UPDATE and DELETE operations rely on the Replica Identity to locate target rows on the subscriber side. By default, tables use `REPLICA IDENTITY DEFAULT`, where the system relies on the Primary Key to locate rows. If a table has no primary key, the replication strategy falls back to `REPLICA IDENTITY NOTHING`, causing UPDATE and DELETE operations to fail because the target rows cannot be located.
11+
12+
IvorySQL introduces the GUC parameter `logical_replication_fallback_to_full_identity`. When this parameter is enabled, if a table's replica identity is `DEFAULT` and it has no primary key, the system automatically falls back to `REPLICA IDENTITY FULL` — recording the complete old row data in the WAL, allowing UPDATE and DELETE operations on tables without a primary key to work correctly through logical replication.
13+
14+
This feature only takes effect on the publisher side; no additional configuration is required on the subscriber side.
15+
16+
== Parameter Description
17+
18+
[source,sql]
19+
----
20+
# postgresql.conf
21+
logical_replication_fallback_to_full_identity = on
22+
----
23+
24+
Parameter description:
25+
26+
- Type: `boolean`;
27+
- Default value: `off`;
28+
- Scope: `sighup`; takes effect by modifying `postgresql.conf` and executing `SELECT pg_reload_conf();`, no database restart required;
29+
- Applicable node: only takes effect on the publisher side; no configuration needed on the subscriber side.
30+
31+
== Test Cases
32+
33+
=== Without the Parameter Enabled, UPDATE and DELETE Replication Fails for Tables Without Primary Key
34+
35+
[source,sql]
36+
----
37+
-- Publisher: create a table without a primary key
38+
CREATE TABLE test_no_pk (id int, name text);
39+
40+
-- Subscriber: create the same table structure
41+
CREATE TABLE test_no_pk (id int, name text);
42+
43+
-- Publisher: create a publication and add the table
44+
CREATE PUBLICATION tap_pub FOR TABLE test_no_pk;
45+
46+
-- Subscriber: create a subscription
47+
CREATE SUBSCRIPTION tap_sub CONNECTION 'host=publisher dbname=postgres' PUBLICATION tap_pub;
48+
49+
-- Publisher: INSERT operations always work (do not depend on replica identity)
50+
INSERT INTO test_no_pk VALUES (1, 'alice');
51+
52+
-- Publisher: UPDATE fails (no primary key, cannot locate the target row)
53+
UPDATE test_no_pk SET name = 'bob' WHERE id = 1;
54+
-- ERROR: cannot update table "test_no_pk" because it does not have a replica identity and publishes updates
55+
56+
-- Publisher: DELETE also fails
57+
DELETE FROM test_no_pk WHERE id = 1;
58+
-- ERROR: cannot delete from table "test_no_pk" because it does not have a replica identity and publishes deletes
59+
----
60+
61+
=== With the Parameter Enabled, UPDATE and DELETE Replication Works for Tables Without Primary Key
62+
63+
[source,sql]
64+
----
65+
-- Publisher: enable the parameter and reload the configuration
66+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = on;
67+
SELECT pg_reload_conf();
68+
69+
-- Publisher: INSERT works
70+
INSERT INTO test_no_pk VALUES (1, 'alice');
71+
72+
-- Publisher: UPDATE works (automatically records the complete old row data in FULL mode)
73+
UPDATE test_no_pk SET name = 'bob' WHERE id = 1;
74+
75+
-- Publisher: DELETE works
76+
DELETE FROM test_no_pk WHERE id = 1;
77+
----
78+
79+
=== The Parameter Does Not Affect Tables With a Primary Key
80+
81+
[source,sql]
82+
----
83+
-- Tables with a primary key always use the primary key to locate rows, regardless of this parameter
84+
CREATE TABLE test_with_pk (id int PRIMARY KEY, name text);
85+
86+
-- Whether the parameter is enabled or not, UPDATE and DELETE work normally
87+
INSERT INTO test_with_pk VALUES (1, 'alice');
88+
UPDATE test_with_pk SET name = 'bob' WHERE id = 1;
89+
DELETE FROM test_with_pk WHERE id = 1;
90+
----
91+
92+
=== The Parameter Does Not Affect Tables Explicitly Set to REPLICA IDENTITY NOTHING
93+
94+
[source,sql]
95+
----
96+
-- Create a table and explicitly set it to REPLICA IDENTITY NOTHING
97+
CREATE TABLE test_nothing (id int, data text);
98+
ALTER TABLE test_nothing REPLICA IDENTITY NOTHING;
99+
100+
-- Even with logical_replication_fallback_to_full_identity enabled,
101+
-- UPDATE and DELETE still fail (the parameter does not override an explicit NOTHING setting)
102+
INSERT INTO test_nothing VALUES (1, 'test');
103+
UPDATE test_nothing SET data = 'modified' WHERE id = 1;
104+
-- ERROR: cannot update table "test_nothing" because it does not have a replica identity and publishes updates
105+
106+
DELETE FROM test_nothing WHERE id = 1;
107+
-- ERROR: cannot delete from table "test_nothing" because it does not have a replica identity and publishes deletes
108+
----
109+
110+
=== Dynamically Switching the Parameter at Runtime
111+
112+
[source,sql]
113+
----
114+
-- Disable the parameter: UPDATE/DELETE on tables without a primary key will revert to error behavior
115+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = off;
116+
SELECT pg_reload_conf();
117+
118+
-- Enable the parameter: UPDATE/DELETE on tables without a primary key will work normally
119+
ALTER SYSTEM SET logical_replication_fallback_to_full_identity = on;
120+
SELECT pg_reload_conf();
121+
----
122+
123+
== Limitations
124+
125+
. This parameter only takes effect for tables whose replica identity is `REPLICA IDENTITY DEFAULT` and that have no primary key; tables explicitly set to `FULL`, `USING INDEX`, or `NOTHING` are not affected;
126+
. When this parameter is enabled, UPDATE and DELETE on tables without a primary key will record the complete old row data in the WAL (same effect as `REPLICA IDENTITY FULL`), which increases WAL size and network traffic compared to recording only primary key columns when a primary key exists;
127+
. This parameter only takes effect on the publisher side; no configuration is needed on the subscriber side;
128+
. INSERT operations do not depend on replica identity and can always be replicated normally, regardless of whether this parameter is enabled;
129+
. This parameter does not replace an explicit `ALTER TABLE ... REPLICA IDENTITY FULL` setting, nor does it override tables explicitly set to `NOTHING`.

0 commit comments

Comments
 (0)