PostgreSQL 逻辑解码(Logical Decoding)实操指南
逻辑解码是 PostgreSQL 提供的核心功能,可将数据库的 WAL(Write-Ahead Log)日志转换为人类可读/机器可解析的格式,常用于数据同步、审计、变更捕获等场景。本文通过实操示例,详细讲解逻辑解码的两种核心控制方式(SQL 接口、流复制协议),以及两阶段事务的解码方法。
一、前置条件配置
使用逻辑解码前,必须完成以下参数配置(修改 postgresql.conf 后重启数据库生效):
| 参数名 | 配置值 | 说明 |
|---|---|---|
wal_level | logical | 开启逻辑解码所需的 WAL 级别(默认是 replica,不支持逻辑解码) |
max_replication_slots | ≥ 1 | 逻辑解码依赖复制槽,需至少分配 1 个槽位 |
max_wal_senders | ≥ 1 | 使用流复制协议(pg_recvlogical)时,需预留 WAL 发送进程 |
max_prepared_transactions | ≥ 1 | 解码两阶段事务时必填 |
权限要求:操作逻辑解码需以超级用户(superuser)身份连接目标数据库。
二、通过 SQL 接口控制逻辑解码
SQL 接口是最直观的逻辑解码方式,核心依赖 pg_create_logical_replication_slot、pg_logical_slot_get_changes 等系统函数。
2.1 创建逻辑复制槽
使用 test_decoding 插件(PostgreSQL 内置)创建名为 regression_slot 的复制槽:
-- 创建复制槽:参数依次为槽名、输出插件、临时槽(false)、支持两阶段(true)
SELECT * FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true);
执行结果:
slot_name | lsn
-----------------+-----------
regression_slot | 0/16B1970
(1 row)
2.2 查看复制槽信息
通过系统视图 pg_replication_slots 验证复制槽创建结果:
SELECT slot_name, plugin, slot_type, database, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;
执行结果:
slot_name | plugin | slot_type | database | active | restart_lsn | confirmed_flush_lsn
-----------------+---------------+-----------+----------+--------+-------------+-----------------
regression_slot | test_decoding | logical | postgres | f | 0/16A4408 | 0/16A4440
(1 row)
2.3 解码数据库变更
(1)解码 DDL 操作(无实际数据变更)
逻辑解码不复制 DDL 本身,仅记录 DDL 对应的事务起止:
-- 无变更时,解码结果为空
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
-- 创建测试表(DDL 操作)
CREATE TABLE data(id serial primary key, data text);
-- 解码 DDL 对应的事务
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-------+--------------
0/BA2DA58 | 10297 | BEGIN 10297
0/BA5A5A0 | 10297 | COMMIT 10297
(2 rows)
(2)解码 DML 操作(INSERT)
DML 操作会被完整解码,包含具体的变更内容:
-- 执行 INSERT 事务
BEGIN;
INSERT INTO data(data) VALUES('1');
INSERT INTO data(data) VALUES('2');
COMMIT;
-- 解码 DML 变更
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-------+---------------------------------------------------------
0/BA5A688 | 10298 | BEGIN 10298
0/BA5A6F0 | 10298 | table public.data: INSERT: id[integer]:1 data[text]:'1'
0/BA5A7F8 | 10298 | table public.data: INSERT: id[integer]:2 data[text]:'2'
0/BA5A8A8 | 10298 | COMMIT 10298
(4 rows)
2.4 消费型解码 vs 预览型解码
| 函数 | 特性 | 适用场景 |
|---|---|---|
pg_logical_slot_get_changes | 读取后消费变更(不可重复读取) | 正式消费变更数据 |
pg_logical_slot_peek_changes | 读取后不消费变更(可重复读取) | 调试、预览变更内容 |
预览型解码示例:
-- 插入测试数据
INSERT INTO data(data) VALUES('3');
-- 第一次预览(返回变更)
SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL);
-- 第二次预览(仍返回相同变更)
SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL);
-- 传递插件参数(如包含时间戳)
SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-timestamp', 'on');
带时间戳的执行结果:
lsn | xid | data
-----------+-------+---------------------------------------------------------
0/BA5A8E0 | 10299 | BEGIN 10299
0/BA5A8E0 | 10299 | table public.data: INSERT: id[integer]:3 data[text]:'3'
0/BA5A990 | 10299 | COMMIT 10299 (at 2017-05-10 12:07:21.272494-04)
(3 rows)
2.5 删除复制槽
不再使用的复制槽需手动删除,避免占用数据库资源:
SELECT pg_drop_replication_slot('regression_slot');
三、通过流复制协议(pg_recvlogical)控制逻辑解码
pg_recvlogical 是 PostgreSQL 内置的命令行工具,通过流复制协议实时消费逻辑解码数据,适合生产环境的持续同步场景。
3.1 基础示例(普通事务)
# 1. 创建复制槽
pg_recvlogical -d postgres --slot=test --create-slot
# 2. 启动实时解码(-f - 表示输出到标准输出)
pg_recvlogical -d postgres --slot=test --start -f -
# 3. (另一个终端)插入测试数据
psql -d postgres -c "INSERT INTO data(data) VALUES('4');"
# 4. 回到解码终端,可看到实时输出:
# BEGIN 693
# table public.data: INSERT: id[integer]:4 data[text]:'4'
# COMMIT 693
# 5. 停止解码(Ctrl+C),删除复制槽
pg_recvlogical -d postgres --slot=test --drop-slot
3.2 两阶段事务解码示例
两阶段事务(PREPARE/COMMIT PREPARED)需在创建复制槽时指定 --two-phase:
# 1. 创建支持两阶段的复制槽
pg_recvlogical -d postgres --slot=test --create-slot --two-phase
# 2. 启动实时解码
pg_recvlogical -d postgres --slot=test --start -f -
# 3. (另一个终端)执行两阶段事务
psql -d postgres -c "BEGIN;INSERT INTO data(data) VALUES('5');PREPARE TRANSACTION 'test';"
# 4. 解码终端输出:
# BEGIN 694
# table public.data: INSERT: id[integer]:5 data[text]:'5'
# PREPARE TRANSACTION 'test', txid 694
# 5. (另一个终端)提交已准备的事务
psql -d postgres -c "COMMIT PREPARED 'test';"
# 6. 解码终端输出:
# COMMIT PREPARED 'test', txid 694
# 7. 停止解码,删除复制槽
pg_recvlogical -d postgres --slot=test --drop-slot
四、两阶段事务的 SQL 接口解码
除 pg_recvlogical 外,也可通过 SQL 接口解码两阶段事务,核心是创建复制槽时开启 two-phase 参数。
4.1 提交已准备的事务
-- 执行两阶段事务
BEGIN;
INSERT INTO data(data) VALUES('5');
PREPARE TRANSACTION 'test_prepared1';
-- 解码 PREPARE 阶段
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-----+---------------------------------------------------------
0/1689DC0 | 529 | BEGIN 529
0/1689DC0 | 529 | table public.data: INSERT: id[integer]:3 data[text]:'5'
0/1689FC0 | 529 | PREPARE TRANSACTION 'test_prepared1', txid 529
(3 rows)
-- 提交已准备的事务
COMMIT PREPARED 'test_prepared1';
-- 解码 COMMIT 阶段
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-----+--------------------------------------------
0/168A060 | 529 | COMMIT PREPARED 'test_prepared1', txid 529
(1 row)
4.2 回滚已准备的事务
-- 执行两阶段事务
BEGIN;
INSERT INTO data(data) VALUES('6');
PREPARE TRANSACTION 'test_prepared2';
-- 解码 PREPARE 阶段
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-----+---------------------------------------------------------
0/168A180 | 530 | BEGIN 530
0/168A1E8 | 530 | table public.data: INSERT: id[integer]:4 data[text]:'6'
0/168A430 | 530 | PREPARE TRANSACTION 'test_prepared2', txid 530
(3 rows)
-- 回滚已准备的事务
ROLLBACK PREPARED 'test_prepared2';
-- 解码 ROLLBACK 阶段
SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
执行结果:
lsn | xid | data
-----------+-----+----------------------------------------------
0/168A4B8 | 530 | ROLLBACK PREPARED 'test_prepared2', txid 530
(1 row)
五、核心注意事项
- 逻辑解码仅捕获事务性变更,DDL 操作仅记录事务起止,不包含 DDL 内容;
pg_logical_slot_get_changes读取后会消费变更(不可重复读取),pg_logical_slot_peek_changes仅预览(可重复读取);- 复制槽需手动删除,否则会持续保留 WAL 日志,可能导致磁盘占满;
- 两阶段事务解码需提前配置
max_prepared_transactions ≥ 1,且创建复制槽时开启two-phase参数。
总结
- 逻辑解码的核心前提是配置
wal_level = logical和足够的复制槽/ WAL 发送进程,且需超级用户权限; - 控制逻辑解码有两种方式:SQL 接口(适合调试/单次查询)、
pg_recvlogical流协议(适合实时消费); - 两阶段事务解码需额外配置
max_prepared_transactions,并在创建复制槽时开启两阶段支持,可分别解码 PREPARE/COMMIT/ROLLBACK 阶段。