WAL / CDC / 复制槽 /位点

WAL / CDC / 复制槽


一、WAL 是什么

WAL = Write-Ahead Log,预写日志。就是数据库的一本流水账。

PostgreSQL 有条铁律:

先把要做的事记在账上,再真去做。

你执行 UPDATE sale.merchants SET name='张三' WHERE id=123,数据库实际干的是:

复制代码
① 往账本写一行:「merchants 表 id=123 的 name 改成张三」
② 这行落盘(fsync)
③ 然后才去改真正的数据文件

为什么要这么绕? 为了崩溃恢复。如果 ② 之后 ③ 之前断电,重启时读账本就能把没做完的补上。

关键点:账本记的是「谁、什么时候、改了什么」------ 这正好就是「变化」本身。

所以「同步数据」这件事,本质上就是读这本账。


二、CDC 是什么

CDC = Change Data Capture,变更数据捕获。

要解决的场景:两个库要保持一致(同步 / 数仓 / 审计),但不能每 5 分钟全量拷一遍。

三种做法:

做法 原理 问题
全量快照 定期 SELECT 全表 数据量大跑不完;跑的过程中源端还在变,永远对不齐
触发器 在表上挂触发器,改一行记一条 拖慢业务写入;DDL 变更要跟着改触发器;漏掉 TRUNCATE 之类
读日志 ★ 读数据库自己的流水账 对业务几乎零侵入,最完整

现在说的 CDC 基本都指第三种 ------ 读 WAL。

读日志的好处:不用改表结构、不影响业务性能、什么变更都能拿到(INSERT/UPDATE/DELETE/TRUNCATE)。

代价:日志是二进制私有的,要「翻译」。


三、逻辑解码

问题:WAL 是二进制格式,而且是给数据库自己用的。

它记的是「第几号文件第几页第几个字节改成什么」,不是「merchants 表 id=123 的 name 改成张三」。

所以需要一个翻译官,把账本翻译成「表 + 行 + 新值」。

这个翻译官叫 逻辑解码插件(output plugin):

插件 谁在用
pgoutput PostgreSQL 内置(publication/subscription)
pglogical_output pglogical
test_decoding 调试用的,把变更打成文本

翻译官翻译的时候会「挑」:

复制代码
账本上:  [你的表] [别人的表] [系统表] [你的表] [别人的表]
            ↓         ↓         ↓        ↓        ↓
翻译官:    发      扔掉      扔掉      发      扔掉

它只发「订阅范围里」的表。范围怎么定?

  • 内置复制:看 publication(发布)
  • pglogical:看 replication set(复制集)

四、复制槽

为什么需要

账本一直在写、一直在滚。写完的旧账本,数据库觉得没用了会回收掉(删文件、腾磁盘)。

但翻译官要是还没读完就被回收了怎么办?

所以要有个东西告诉数据库:

「我还读到这儿呢,从这儿往后的先别扔。」

这个东西就是复制槽(replication slot)。

是什么

槽不是容器,是书签。 它不存数据,只记一个位置。

复制代码
槽 = 插在流水账上的一个夹子:「夹子往后的先别扔」

真正占磁盘的是 WAL,不是槽。 槽只是让 WAL 不被回收。

两种槽

物理槽 逻辑槽
用在哪 流复制(主备) 逻辑复制(CDC)
解码 不解码,原样传 走解码插件
有没有「确认点」 没有 有

CDC 用的都是逻辑槽。

槽里的变量

变量 含义
restart_lsn 夹子 ------ 这个位置之后的 WAL 不能回收
confirmed_flush_lsn 确认点 ------ 消费者说它收到哪了
catalog_xmin 挡住 vacuum,别清掉解码还要用的系统表旧版本

什么时候创建 / 删除

槽跟着订阅的生命周期走,不用手动管:

  • 建订阅成功 → 立刻创建
  • 删订阅 → 立刻删除

槽会拖累什么

一个活着的逻辑槽会:

行为 后果
持有 WAL(订阅跟不上时) 占磁盘
挡住 vacuum(catalog_xmin) 表和系统表膨胀

追平状态下槽只落后几十字节 (实测 56 字节)。只有订阅挂了才会涨。


五、位点(LSN)

LSN = Log Sequence Number,就是一个位置标记。

长这样:0/231BD810 ------ 十六进制,越大越靠后。

有几个位点

复制代码
① 账本写到哪          一直往前走 ── 数据库在记账,跟谁都没关系
        ↓
② 解码器读到哪        一直往前走 ── 它必须读过去才知道下一条是不是你的表
        ↓
③ confirmed_flush_lsn  等消费者回报才走    ← 走得快
        ↓
④ restart_lsn(夹子)  比确认点更保守       ← 走得慢,★ 决定磁盘

你在 pg_replication_slots 里能看到的,是 ③ 和 ④。① 和 ② 看不到。

什么时候走

只要有 WAL 记录写出来,位点就走。

谁写的 位点走吗 会传到目标端吗
你的表 ✅ ✅
别人的表 ✅ ❌
数据库自己(checkpoint / autovacuum) ✅ ❌
DDL ✅ ❌

实测数据:

事件 位点走多少
写 1 行 280 字节
UPDATE 1 行 128 字节
一次事务 100 行 13 KB
完全空闲 20 秒 56 字节
建个索引 3.6 KB

看最后两行 ------ 你不动,位点也走,因为数据库后台自己也在写账本。

什么时候不走(★ 关键)

只有一种:消费者不回报了。

复制代码
订阅挂了  →  没人回报  →  ③④ 冻住  →  夹子后面的 WAL 不能回收  →  磁盘涨

实测对照:

复制代码
订阅正常跑:                    夹子落后 56 字节
停掉订阅 + 往别的表写 20 万行:  落后涨到 63 MB
恢复订阅:                      立刻降回 56 字节

为什么「别的表在写、我的表不动」位点也走

因为位点属于整条 WAL 流,不属于你那几张表。

复制代码
位点 = 翻译官读到哪了       (整条流)
传输 = 翻译官说了什么       (只有你的表)

消费者回报的是「这条流我到 X 了」------ 是位置,不是数据清单。

所以哪怕一段区间里全是别人的表,它照样回报,位点照样走。

关键:位点冻住 = 数据能补回来

复制代码
停机期间写的变更 → 变成 WAL 堆在那儿
                  ↓
           这不是「垃圾」,是「还没送出去的货」
                  ↓
重启后从冻结的位置接着读 → 全部补发

正因为确认点冻着,数据才补得回来。 如果它会自己往前跑,停机期间的变更就永久丢了。


六、一次同步的完整流程

复制代码
【源端】                                        【目标端】
  业务写数据
      ↓
  ══════ WAL ══════  一直写
      ↓
  解码器从头往后读 ── 不在范围内的表:扔掉
      ↓               在范围内的表:发出去 ──────▶ 收到
      ↓                                          ↓
      ↓                                      写进目标表
      ↓                                          ↓
  ◀────────── 回报「我到 X 了」──────────────────┘
      ↓
  确认点 / 夹子 往前挪
      ↓
  夹子之前的 WAL 可以回收(磁盘释放)

首同步(初始全量)是另一回事 ------ 一次性把历史数据 COPY 过去,之后才走上面这个增量流程。


七、最容易搞混的六个点

错误理解 正确
槽是容器,存数据 槽是书签,只记位置;占磁盘的是 WAL
位点是给某几张表的 位点属于整条 WAL 流
confirmed_flush_lsn 一直往前走 它等回报,没人回报就冻住
读到范围内的表要等确认才能读下一条 不停,一直读一直发;等确认的是那两个变量
别的表一直写会把我的槽撑爆 不会 ------ 只要订阅活着,别的表怎么写都不影响
槽要手动创建 跟着订阅生命周期,不用管

八、一句话记忆

WAL 数据库的流水账,记的是变化本身
CDC 读这本账,别用触发器和全量快照
解码插件 把二进制账本翻译成人话,顺便「挑」该发的
复制槽 插在账本上的书签,防止还没读就被回收
位点 整条流的位置,不是你那几张表的位置
磁盘涨 只有一种原因 ------ 没人回报,夹子冻住了

再往下看

相关推荐
草莓熊Lotso1 小时前
【C++ 基于微服务的即时通讯系统】基建三件套:gflags + gtest + spdlog 源码编译安装与实战入门
linux·网络·数据库·c++·微服务·log4j
bksczm1 小时前
Redis基础篇之初识分布式
数据库·redis·分布式
for_ever_love__1 小时前
MySQL 索引类型全解:B+Tree / Hash / R-Tree / 全文 的底层差异,以及一次性建对的选型表
数据库·mysql·b+树·唯一索引·全文索引·索引类型·索引分类
仍然.10 小时前
Redis---缓存
数据库·redis·缓存
Aloudata10 小时前
LookML 语义模型 vs 企业级独立语义层:BI 建模语言能否承担企业语义底座?
数据库·人工智能·数据分析·数据资产·dataagent
细嗅蔷薇@10 小时前
排序和分⻚(order by 、limit)
数据库·oracle
wdfk_prog10 小时前
LWIP教程 09:从 `tcp_slowtmr()` 到 Fast Retransmit——TCP 超时重传与重复 ACK
服务器·网络·数据库·网络协议·tcp/ip
逸模10 小时前
BIM在连锁餐饮装修中的应用:不只是画三维图
大数据·数据库·人工智能·物联网·建模
梦想平凡10 小时前
棋牌游戏技术实战(五):从对局事件重新执行规则,核对棋盘和最终状态
数据库·游戏·oracle