深入浅出 SQL Server CDC 数据同步
简介
SQL Server 是一款老牌关系型数据库,自 1988 年由 Microsoft、Sybase 和 Ashton-Tate 三家公司共同推出,不断迭代更新至今,拥有相当广泛的用户群体。
如今,我们提到 SQL Server 通常指 Microsoft SQL Server 2000 之后的版本。
SQL Server 2008 是一个里程碑版本,加入了大量新特性,包括 新的语法 、 更丰富的类型 以及本文所提及的 CDC 能力 ,这个能力让数据从 SQL Server 实时同步到外部更加方便。
本文将介绍 CloudCanal 在新版本中对于 SQL Server 数据同步更进一步的优化和实践。
SQL Server CDC 长什么样?
原始日志
常见的数据库往往存在以下两种日志
- redo 日志
- 记录数据的正向变更,简单来说,事务的 commit 通常先记录在这个文件,再返回应用程序成功,可确保数据 持久性
- undo 日志
- 用于保证事务的 原子性 ,如执行 rollback 命令即反向执行 undo 日志中内容以达成数据回滚
一条 DML 语句写入数据库流程如下
- 大部分关系型数据库中,一个或多个变更会被隐式或显式包装成一个事务
- 事务开始,数据库引擎定位到数据行所在的 文件位置 并根据已有的数据生成 前镜像 和 后镜像
- 后镜像 数据记录到 redo 日志中, 前镜像 数据记录到 undo 日志中
- 事务提交后,日志提交位点(检查点)向前推进,已提交的日志内容即可能被覆盖或者释放
SQL Server redo/undo 日志采用了 ldf 格式 ,文件循环使用。
- ldf 日志文件由多个 VLF(逻辑日志) 组合在一起,这些 VLF 首尾相连形成完整的数据库日志记录
- ldf 在逻辑日志末端到达物理日志文件末端时,新的日志记录将回到物理日志文件开始,复写旧的数据
ldf 文件即 CDC 所分析的增量日志文件。
启用 CDC
在数据库上执行
exec [console].sys.sp_cdc_enable_db
命令为 console 数据库启用 CDC 功能,这个语句实际上会创建两个作业:
cdc.console_capture
,
cdc.console_cleanup
使用
exec sp_cdc_help_jobs
命令可查看这两个作业详细信息。
- cdc.console_capture
- 负责分析 ldf 日志 并解析 console 数据库事件,再将其写入到 CDC 表中
- 间隔 5 秒钟执行一次扫描,每次扫描 10 轮,每轮扫描最多 500 个事务
- cdc.console_cleanup
- 负责定期清理 CDC 表中较老的数据
- 默认保留 3 天 CDC 日志数据(4320秒)
开启 CDC 功能后,SQL Server 数据库会多出一个名称为 cdc 的 schema,里面会多出下列这些表。
- change_tables
- 记录每一个启用了 CDC 的 源表 及其对应的 捕获表
- captured_columns
- 记录对应 捕获表 中每个列的信息
- index_columns
- 记录 源表 含有的主键信息(如果有)
- lsn_time_mapping
- 记录每个事务的开始/结束时间及 LSN 位置信息
- ddl_history
- 记录 源表 发生的 增/减列 对应的 DDL 信息,除此之外的 DDL 都不会被记录
有了上述准备动作和信息,即可开始对原始表开启 change data capture(CDC) ,即增量数据捕获了。
捕获表变更
有如下 源表
create table [dbo].[test_table] (
[id] [bigint] NOT NULL primary key,
[test] [nchar](10) NULL
)
执行下列命令即可为它启用 CDC
exec [console].[sys].[sp_cdc_enable_table]
@source_schema = [dbo],
@source_name = [test_table],
@role_name = NULL,
@capture_instance = [dbo_test_table], -- 可选项
@supports_net_changes = 0;
cdc schema 下多出一个名为 dbo_test_table_CT 的表,即 捕获表
-
对
源表
[dbo].[test_table]
做若干 DML 操作,通常是 5 秒内就可在捕获表中看到变更记录 - 对 源表 做一些 增/减 列 操作,对应的 DDL 会出现在 ddl_history 表中
其他表也可通过类似设置,获取到相应的增量变更。整个机制看上去相当直观和简单。
挑战是什么?
难点1:DDL 同步困难
CDC 捕获表只反馈数据的变化,无 DDL 信息
DDL 需额外获取即和 DML 的顺序关系要额外处理
解决这个问题,需要通过执行以下的 SQL 将 DDL 和 DML 事件混合到一起并保证顺序,但是实际使用中会面临严重的性能问题。
select * from (
select __$start_lsn lsn,__$operation oper,__$update_mask mask, null ddl ,id data_id,test data_test
from [console].[cdc].[dbo_test_table_CT]
union
select ddl_lsn lsn, -1 oper,null mask, ddl_command ddl ,null data_id,null data_test
from [console].[cdc].[ddl_history]
) t order by lsn
难点2:无法获取新增列数据
CDC 捕获表的结构并不会随着 DDL 事件的发生而变化,这意味着无法获取新增列的数据
难点3:数据库限制
使用 CDC 功能本身也会产生一些硬性的限制,大致可以分为两类
硬性限制
- 已经启用 CDC 捕获的源表上不能执行 truncate table 语句,执行即报错
- CDC 捕获表本质上也是一个普通的表,大量订阅会导致整库表的数量扩大
- 依赖 SQL Server 代理,如没启动或作业运行失败,捕获表中不会有任何新数据写入
- 一张表只能创建 2 张对应的 CDC 捕获表,即无法做超过 2 个以上的增量订阅
-
一张表的 CDC 捕获只能设置启动和禁止,即不能通过重建 CDC 并指定 LSN 来获取新数据
软性限制
- CDC 捕获表中的数据存留时间默认 3 天
- 在插入或更新超大字段时默认 CDC 只会处理最大 64KB 个字节的数据
- 数据内容如果超过这个限制会导致 CDC 捕获任务报错并停止工作
-
受影响的类型有 7 个:
text
、ntext
、varchar(max)
、nvarchar(max)
、varbinary(max)
、xml
、image
CloudCanal 的解决方法
CloudCanal SQL Server 增量消费基础处理模型如下所述,保证单个表的数据变更顺序,满足大部分场景
-
根据
change_tables
表确定一个工作队列 - 确定起始位点,对于捕获表的增量数据扫描从起始位点开始
- 并发处理工作队列上的事件
- 每个 Worker 会根据起始 LSN 扫描自身要处理的 CDC 捕获表
- 每个 Worker 扫描都会维护自身的 LSN 进度
解决难点1:DML/DDL重排序
CDC 捕获表中的每一条记录都有一个 LSN 信息,
ddl_history
表也有 LSN 信息。因此可以借助
插值
的思想将 DDL 事件插入到正常的 DML 事件序列中去,原理如下图:
-
对
ddl_history
表进行预查询,获取到的 DDL 事件在稍后的处理中会进行位点比对处理 -
查询
dbo_test_table_CT
数据捕获表 - 处理每一条的捕获表的数据时检测 DDL 事件是否可以被插入
- 形成完整的事件流
解决难点2:反查补充缺失数据
SQL Server CDC 捕获表最多只能创建 2 张是硬性限制,但刚好能解决这个问题,在 DDL 发生后创建第二个 CDC 捕获表可以感知到 DDL 对数据的变化
-
创建第一个 CDC 捕获表
dbo_test_table_1_CT
- 在两次数据插入的中间增加一个新的列
-
创建第二个 CDC 捕获表
dbo_test_table_2_CT
- 在插入一条新数据
通过上图可看到
dbo_test_table_2_CT
相比
dbo_test_table_1_CT
已经可以感知到新增的列数据
遗憾的是 DDL 发生后到第二个 CDC 捕获表创建出来之前这中间的数据仍然是缺失的
上面的例子如下图所示(灰色的 Event 表示事件或者数据有缺损)
以 DDL 发生的 LSN 为分界点
- DDL 前 dbo_test_table_1_CT 表数据完全可信
- DDL 后, dbo_test_table_1_CT 表中没有新列字段,数据残缺,不能完全信任
- dbo_test_table_2_CT 在 DDL 后创建,相比较 dbo_test_table_1_CT 表,它的数据有缺失
- dbo_test_table_1_CT 和 dbo_test_table_2_CT 之间还存在 一个盲区 导致两个表都不可信
CloudCanal 解决办法是在此基础上将两张表都缺损的位点 反向使用 PK 从源表中补齐 的方式解决这个问题(上图中深灰色部分)
有一个极端情况是在第二张 CDC 捕获表创建过程中发生了新的 DDL ,这会导致新创建的捕获表也不可靠,因此需要重新创建第二个 CDC 捕获表,并且扩大反查补齐的数据范围(下图中深灰色部分)
CloudCanal 正是基于上述一系列机制才解决了 DDL 事件导致无法获取增量数据的难题
解决难点3:提供专业优化方案
对于 硬性限制 ,CloudCanal 没有正面解决的方案,而是后续提供更多样的方式(如 trigger , 定时增量扫描 , 新版本SQL Server CDC方案 等)进行补充。
而 软性限制 ,可通过以下方式优化
-
通过以下命令中的
retention
参数来设置 CDC 捕获表中的数据存留时间
-- 单位:秒