知识卡片
四种复制日志实现方式在耦合度与兼容性上的权衡
内容
主库把变更传给从库具体传什么内容,有四种实现方式,权衡点主要是”复制协议和存储 引擎耦合得多紧”。基于语句的复制:主库把执行的每条SQL语句(INSERT/UPDATE/DELETE) 转发给从库重新执行;最紧凑,但会被非确定性函数(NOW()、RAND())、自增列、有副作用 的触发器等破坏,边缘情况太多,现在较少作为默认方式。传输预写式日志(WAL):直接 把[[LSM树用内存表加WAL兼顾写入速度与崩溃安全]][[B树用固定大小页面树保证O-logn深度 并靠WAL防崩溃损坏]]里提到的、存储引擎本身用于崩溃恢复的仅追加字节序列发给从库, 从库重放后建立一模一样的数据结构;PostgreSQL、Oracle用这种方式,缺点是WAL记录的是 “哪个磁盘块哪些字节变了”这类极底层信息,把复制协议和存储引擎的内部格式死死绑在 一起——如果存储格式跨版本变了,主从库往往不能跑不同版本的数据库软件,也就没法 通过”先升级从库、故障切换、再升级原主库”实现零停机升级。逻辑日志复制(基于行): 用一种独立于存储引擎内部表示的日志格式,以行的粒度描述写入(插入记录所有列新值、 删除记录能定位该行的信息、更新记录定位信息+变更列新值);因为和存储引擎解耦, 更容易保持向后兼容、允许主从运行不同版本甚至不同存储引擎,对外部系统也更容易 解析——这正是”数据变更捕获”技术的基础。基于触发器的复制:把复制逻辑完全移到 应用层,用数据库自带的触发器把变更记录到一张表、再由外部程序读取处理,最灵活 (能只复制部分数据、能跨异构数据库、能自定义冲突解决逻辑),但开销最高、也最容易 出错。
结构图:
flowchart TD
A[基于语句复制] -->|最紧凑, 但非确定性函数会出错| A1[已较少作为默认方式]
B[传输WAL] -->|与存储引擎内部格式紧耦合| B1[主从版本必须匹配, 难零停机升级]
C[逻辑日志/基于行复制] -->|与存储引擎解耦| C1[可跨版本/跨存储引擎, 支撑数据变更捕获]
D[基于触发器复制] -->|复制逻辑移到应用层| D1[最灵活但开销大易出错]
参考来源
- 位置:《数据密集型应用系统设计》第五章《复制》"复制日志的实现"(源文件:
_epub-src/ch5_split_001.html)
- 结论依据:原文详述基于语句复制易被非确定性函数破坏、传输WAL与存储引擎紧耦合
导致难以跨版本升级、逻辑日志复制与存储引擎解耦支撑数据变更捕获、基于触发器
复制把逻辑移到应用层最灵活但开销高,直接支撑本卡片的四种方式结构梳理。
- 原始内容:任何调用非确定性函数的语句,可能会在每个副本上生成不同的值……WAL
包含哪些磁盘块中的哪些字节发生了更改。这使复制与存储引擎紧密耦合……由于逻辑
日志与存储引擎内部分离,因此可以更容易地保持向后兼容……基于触发器的复制通常
比其他复制方法具有更高的开销。