一、前置准备
使用前,需要在 PostgreSQL 侧完成以下 6 步。示例中的 <db>、<schema>、<table>、<user>、<password>、<slot_name> 请替换为实际值。
二、1. 开启逻辑解码
连接器通过逻辑解码从 WAL 中读取行级变更(INSERT / UPDATE / DELETE),需要将 wal_level 设为 logical。
查看当前配置:
SHOW wal_level; — 必须为 logicalSHOW max_replication_slots; — 复制槽总数上限SHOW max_wal_senders; — WAL 发送进程数上限
自建 PostgreSQL:
ALTER SYSTEM SET wal_level = logical;ALTER SYSTEM SET max_replication_slots = 10;ALTER SYSTEM SET max_wal_senders = 10;
修改 wal_level 后必须重启实例才能生效(该操作可能导致服务中断)
云数据库 PostgreSQL:在控制台的实例参数设置中,将 wal_level 改为 logical,然后重启实例(该操作可能导致服务中断)。
max_replication_slots 应不小于本实例上的 CDC 作业数与其他逻辑订阅数之和,并留出余量;max_wal_senders 应不小于 max_replication_slots。
三、2. 确认逻辑解码插件
逻辑解码插件将 WAL 转换为连接器可读取的格式,通过 decoding.plugin.name 指定,支持以下两种:
| 插件 | 是否需要安装 | 说明 |
| pgoutput(默认) | 不需要,PostgreSQL 10 及以上内置 | 推荐。需要先 创建 publication(第4步) |
| decoderbufs | 需要编译安装,并配置 shared_preload_libraries 后重启 | – |
使用默认的 pgoutput 时无需安装。可通过创建测试复制槽确认插件可用,确认后立即删除:
SELECT pg_create_logical_replication_slot('oceanus_check_slot', 'pgoutput');SELECT pg_drop_replication_slot('oceanus_check_slot');
四、3. 创建同步账号并授权
建议创建专用的同步账号,无需超级用户权限。以下语句由高权限账号(自建库为 superuser,云数据库为控制台创建的高权限账号)执行:
— 创建账号。REPLICATION 用于建立复制连接、创建和读取复制槽CREATE ROLE <user> WITH LOGIN REPLICATION PASSWORD '<password>';– 连接目标库GRANT CONNECT ON DATABASE <db> TO <user>;– 访问 schemaGRANT USAGE ON SCHEMA <schema> TO <user>;– 全量快照读取:授权整个 schema,或只授权要同步的表GRANT SELECT ON ALL TABLES IN SCHEMA <schema> TO <user>;– 或:GRANT SELECT ON <schema>.<table1>, <schema>.<table2> TO <user>;– 可选:之后新建的表自动授予 SELECT(<table_owner> 为建表账号)ALTER DEFAULT PRIVILEGES FOR ROLE <table_owner> IN SCHEMA <schema> GRANT SELECT ON TABLES TO <user>;
| 权限 | 用途 | 缺少时的现象 |
| LOGIN | 登录数据库 | 无法连接 |
| REPLICATION | 建立复制连接、创建复制槽、读取增量 | 增量阶段连接失败 |
| CONNECT ON DATABASE | 连接目标库 | 无法连接目标库 |
| USAGE ON SCHEMA | 访问 schema 下的对象 | 找不到表 |
| SELECT ON TABLE | 读取全量快照 | 快照阶段报权限错误 |
验证:
SELECT rolname, rolcanlogin, rolreplication FROM pg_roles WHERE rolname = '<user>';– rolcanlogin 和 rolreplication 都应为 t
五、4. 创建 publication(使用 pgoutput 时必须)
pgoutput 只输出 publication 中所含表的变更。连接器默认使用名为 dbz_publication 的 publication,不存在时会尝试自动创建包含全库所有表的 publication,该操作需要超级用户权限。建议由高权限账号预先创建。
— 推荐:只发布需要同步的表CREATE PUBLICATION dbz_publication FOR TABLE <schema>.<table1>, <schema>.<table2>;– 之后新增同步表ALTER PUBLICATION dbz_publication ADD TABLE <schema>.<new_table>;
FOR TABLE 方式需要执行账号对目标库有 CREATE 权限,且为表的 owner;FOR ALL TABLES 方式需要超级用户(云数据库为 tencentdb_superuser)。
不建议使用 FOR ALL TABLES。库中无主键且未设置 REPLICA IDENTITY 的表,执行 UPDATE / DELETE 时会报错(cannot update table … because it does not have a replica identity and publishes updates),影响源库业务。
publication 需覆盖 tables 匹配到的所有表。未包含的表只能读到全量数据,读不到增量数据。
验证:
SELECT * FROM pg_publication_tables WHERE pubname = 'dbz_publication';
如需让不同作业使用不同的 publication,可配置 debezium.publication.name。
六、5. 设置表的 REPLICA IDENTITY(按需)
REPLICA IDENTITY 决定 UPDATE / DELETE 时 WAL 中记录的旧行内容。
| 取值 | WAL 中记录的旧行内容 | 适用场景 |
| DEFAULT(默认) | 仅主键列 | 表有主键,且下游是主键表(如 Doris 主键模型)。一般不用修改 |
| FULL | 整行所有列 | 无主键表;或下游需要完整的 UPDATE 前镜像 |
| NOTHING | 不记录 | 不能用于 CDC |
— 需要时设为 FULL。会增加 WAL 体积,大表或高频更新的表需评估ALTER TABLE <schema>.<table> REPLICA IDENTITY FULL;
验证:
SELECT relname, relreplident — d=default, f=full, n=nothing, i=indexFROM pg_classWHERE relnamespace = '<schema>'::regnamespace AND relkind IN ('r', 'p');
七、6. 确认网络连通
·云数据库 PostgreSQL:在安全组中放通 Oceanus 集群网段访问 5432 端口。
·自建 PostgreSQL:在 pg_hba.conf 中允许 Oceanus 网段连接目标数据库,然后执行 SELECT pg_reload_conf(); 重新加载。
八、复制槽(slot.name)
·无需手动创建。作业首次启动时自动创建,账号需具备 REPLICATION 属性。
·名称只能包含小写字母、数字和下划线,不超过 63 个字符。
·每个作业必须使用独立的复制槽。多个作业共用一个复制槽会导致数据丢失或读取异常。
九、版本说明
| Flink 版本 | 说明 |
| 1.20 | 基于 Flink-cdc 社区 release-3.6 开发,JDK 版本为 11,支持 PostgreSQL 数据库 |
十、使用范围
可作为 Source(type: postgres)使用,不支持作为 Sink。
暂不支持同步表结构变更。PostgreSQL 的 WAL 日志中不包含可解析的表结构变更记录,因此上游发生 DDL 变更时不会传递到下游。
十一、Postgres 同步到 Doris
source: type: postgres name: Postgres Source hostname: 127.0.0.1 port: 5432 username: postgres password: pass tables: adb.\\.*.\\.* decoding.plugin.name: pgoutput slot.name: pgtestsink: type: doris name: Doris Sink fenodes: 127.0.0.1:8030 username: root password: passpipeline: name: Postgres to Doris Pipeline parallelism: 4
十二、Source 配置项
| 参数 | 是否必填 | 默认值 | 类型 | 说明 |
| hostname | required | (none) | String | PostgreSQL 数据库服务器的 IP 地址或主机名。 |
| port | optional | 5432 | Integer | PostgreSQL 数据库服务器的端口号。 |
| username | required | (none) | String | 连接 PostgreSQL 数据库服务器时使用的用户名。 |
| password | required | (none) | String | 连接 PostgreSQL 数据库服务器时使用的密码。 |
| tables | required | (none) | String | 需要捕获的表名,格式为 库名.模式名.表名,支持正则表达式,多条规则用英文逗号分隔。注意:点号(.)是库名、模式名与表名的分隔符,如果需要在正则表达式中用点号匹配任意字符, 必须用反斜杠转义。例如:db0.\\.*.\\.*, db1.public.user_table_[0-9]+所有表名必须使用同一个 database,否则作业启动校验失败。若按该规则匹配不到任何表,作业启动时会直接报错。 |
| slot.name | required | (none) | String | 为流式读取变更而在服务端创建的逻辑复制槽名称。每个作业应使用独立的复制槽。 |
| decoding.plugin.name | optional | pgoutput | String | 服务端已安装的逻辑解码插件名称,可取 pgoutput 或 decoderbufs。 |
| tables.exclude | optional | (none) | String | 需要排除的表名,在 tables 匹配结果之上做二次排除,用法与 tables 相同。排除后若无剩余表,作业启动校验失败。 |
| server-time-zone | optional | (none) | String | 数据库服务器的会话时区,例如 Asia/Shanghai。未配置时使用作业运行环境的系统时区, 对时间相关字段可能造成数据不一致,建议显式配置。 |
| table-id.include-database | optional | false | Boolean | 生成的 Table ID 是否包含 database 名。为 true 时格式为 (database, schema, table);为 false 时为 (schema, table)。 |
| scan.startup.mode | optional | initial | String | 启动模式,可取 initial、latest-offset、 snapshot、committed-offset,详见「启动模式」章节。取值大小写不敏感。 |
| scan.incremental.snapshot.chunk.size | optional | 8096 | Integer | 快照阶段单个 chunk 的行数。快照读取时表会被切分为多个 chunk。 |
| scan.snapshot.fetch.size | optional | 1024 | Integer | 读取表快照时每次拉取的最大行数。 |
| scan.incremental.snapshot.backfill.skip | optional | true | Boolean | 快照阶段是否跳过 backfill。跳过之后,快照期间发生的变更会在增量阶段回放, 而不是合并进快照结果。注意:跳过 backfill 可能带来数据不一致,只能保证 at-least-once 语义。 |
| scan.lsn-commit.checkpoints-num-delay | optional | 3 | Integer | 开始提交 LSN 位点前延迟的 checkpoint 次数。增大该值后,全局复制槽消费到的位点会在多个 checkpoint 延迟之后才提交, 有利于在流式阶段持续回收日志文件。 |
| schema-change.enabled | optional | false | Boolean | 是否在处理 pgoutput 的 Relation 消息时推断 CDC 列类型。 |
| metadata.list | optional | (none) | String | 需要额外透传到下游的元数据列表,英文逗号分隔, 可取 op_ts、table_name、database_name、schema_name, 详见「支持的元数据列」章节。 |
| connect.timeout | optional | 30s | Duration | 连接 PostgreSQL 服务器的超时时间。 |
| connect.max-retries | optional | 3 | Integer | 建立 PostgreSQL 连接的最大重试次数。 |
| connection.pool.size | optional | 20 | Integer | 连接池大小。 |
| heartbeat.interval | optional | 30s | Duration | 发送心跳事件的间隔,用于跟踪最新可用的 WAL 位点。 |
| chunk-meta.group.size | optional | 1000 | Integer | chunk 元数据的分组大小,超过该大小时元数据会被拆成多个分组。 |
| chunk-key.even-distribution.factor.upper-bound | optional | 1000.0 | Double | chunk 切分键分布因子的上界。分布因子按 (MAX(id) – MIN(id) + 1) / rowCount 计算, 用于判断表数据是否均匀分布。取值必须大于等于 1.0。 |
| chunk-key.even-distribution.factor.lower-bound | optional | 0.05 | Double | chunk 切分键分布因子的下界,取值必须落在 [0, 1] 区间内。 |
| scan.incremental.close-idle-reader.enabled | optional | false | Boolean | 快照结束后是否关闭空闲的 Reader。依赖 FLIP-147(Checkpoints After Tasks Finished), Flink 版本需大于等于 1.14。 |
| scan.incremental.snapshot.unbounded-chunk-first.enabled | optional | false | Boolean | 快照阶段是否优先分配 unbounded chunk,有助于降低 TaskManager 在快照阶段发生 OOM 的风险。 |
| jdbc.properties.* | optional | (none) | String | 透传给 JDBC URL 的自定义属性,例如 jdbc.properties.ssl: 'true'。 |
| debezium.* | optional | (none) | String | 透传给 Debezium Embedded Engine 的属性。 |
十三、启动模式
scan.startup.mode 决定作业从哪里开始读取数据,取值如下(大小写不敏感):
·initial(默认):首次启动时先对捕获的表做全量快照,快照完成后继续读取最新 WAL。
·latest-offset:跳过快照,只从 WAL 末尾开始读取,即只消费作业启动之后产生的变更。
·snapshot:只做快照阶段,不进入增量阶段,快照读取完成后作业退出。
·committed-offset:跳过快照,从复制槽中已确认提交的位点开始读取。
十四、支持的元数据列
通过 metadata.list 可以指定透传到下游的元数据,可在 transform 中直接引用。
| Key | 类型 | 说明 |
| op_ts | TIMESTAMP_LTZ(3) NOT NULL | 该行数据在数据库中发生变更的时间。如果该记录读取自表的快照而非变更流,则该值始终为 0。 |
| table_name | STRING NOT NULL | 包含该行数据的表名。 |
| schema_name | STRING NOT NULL | 包含该行数据的 schema 名。 |
| database_name | STRING NOT NULL | 包含该行数据的 database 名。 |
配置示例:
source: type: postgres metadata.list: op_ts,table_name,database_name,schema_name
十五、数据类型映射
Postgres 作为 Source 时,按下表把读取到的 PostgreSQL 字段类型转换为 CDC 类型。
| PostgreSQL 类型 | CDC 类型 | 说明 |
| BOOLEAN | BOOLEAN | – |
| BIT(1)VARBIT(1) | BOOLEAN | – |
| BIT(n),n > 1VARBIT(n),n > 1 | BINARY(n) | – |
| SMALLINTINT2SMALLSERIALSERIAL2 | SMALLINT | – |
| INTEGERSERIAL | INT | – |
| BIGINTBIGSERIALOID | BIGINT | – |
| REALFLOAT4 | FLOAT | – |
| DOUBLE PRECISIONFLOAT8 | DOUBLE | – |
| NUMERIC(p, s)DECIMAL(p, s) | DECIMAL(p, s) / DOUBLE / STRING | 取决于 Debezium 的 decimal 处理模式:precise 模式下,精度在 (18, 38] 区间内映射为 DECIMAL(p, s), 否则(含未显式声明精度的情况)映射为 DECIMAL(38, 18);double 模式映射为 DOUBLE;string 模式映射为 STRING。 |
| MONEY | DECIMAL(38, s) | s 由 Debezium 的 money 小数位数配置决定。 |
| CHAR(n)CHARACTER(n)BPCHAR(n) | CHAR(n) | – |
| VARCHAR(n)CHARACTER VARYING(n) | VARCHAR(n) | – |
| TEXTPOINTUUIDJSONJSONBXMLINETCIDRMACADDRMACADDR8INT4RANGENUMRANGEINT8RANGETSRANGETSTZRANGEDATERANGELTREEGEOMETRYGEOGRAPHYCITEXT枚举类型 | STRING | PostGIS 的 GEOMETRY、GEOGRAPHY 类型以文本形式读出。 |
| HSTORE | STRING 或 MAP(STRING, STRING) | 取决于 Debezium 的 hstore 处理模式:json 模式映射为 STRING, map 模式映射为 MAP(STRING, STRING)。 |
| BYTEA | BYTES 或 STRING | 取决于 Debezium 的 binary 处理模式:bytes 模式映射为 BYTES, base64 / hex 模式映射为 STRING。 |
| TIMESTAMP [(p)]TIMESTAMP WITHOUT TIME ZONE [(p)] | TIMESTAMP [(p)] | – |
| TIMESTAMPTZTIMESTAMP WITH TIME ZONE [(p)] | TIMESTAMP_LTZ [(p)] | 由 Debezium 归一化到 UTC 后按带本地时区的时间戳表示。 |
| TIME [(p)]TIME WITHOUT TIME ZONE [(p)] | TIME [(p)] | – |
| DATE | DATE | – |
| INTERVAL | BIGINT 或 STRING | 取决于 Debezium 的 interval 处理模式:numeric 模式映射为 BIGINT(微秒数), string 模式映射为 STRING。 |
| 数组类型(如 INT4[]、TEXT[]) | ARRAY(<元素类型>) | 元素类型按上表规则转换,例如 INT4[] 映射为 ARRAY(INT), TEXT[] 映射为 ARRAY(STRING)。 |
未在上表中出现的字段类型暂不支持,同步包含此类字段的表会导致作业失败。