一、SQLServer CDC Connector 是什么?
SQLServer CDC Connector 是 Flink 提供的一个 Source 连接器,核心能力和 MySQL / Oracle CDC 一样:
- 第一次启动:对目标表做一次快照(历史全量数据);
- 之后:从 CDC 变更表中持续读取增量事件(INSERT/UPDATE/DELETE);
- 在 Flink 侧暴露为一张 动态表(Dynamic Table),可以用 Flink SQL / Table API / DataStream 来消费。
这背后依赖的是 SQL Server 自带的 CDC 功能 + Debezium SQLServer Connector,Flink CDC 负责:
- 管理快照与增量衔接;
- 管理 Checkpoint / 状态;
- 提供 Exactly-Once 语义;
- 提供 SQL DDL 封装、类型映射、指标监控等。
二、依赖与环境准备
2.1 Maven 依赖(项目开发)
在工程中加入:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-sqlserver-cdc</artifactId>
<version>3.5.0</version>
</dependency>
如果你是用 Flink SQL Client / Standalone 集群:
- 下载
flink-sql-connector-sqlserver-cdc-3.5.0.jar; - 放到
<FLINK_HOME>/lib/目录; - 重启 Flink 集群后即可在 DDL 中使用
connector = 'sqlserver-cdc'。
SQL Server 驱动一般由 Flink 所在 JVM 使用的 JDBC 驱动提供(通常是 mssql-jdbc),实际部署时记得确保集群 classpath 中有对应驱动 jar。
三、SQL Server 端准备:启用 CDC
和 Oracle、MySQL 不同,SQL Server 本身就自带 CDC 能力,只是默认是关闭的。要结合 Flink 使用,分两步:
- 对数据库启用 CDC(DB 级别);
- 对具体表启用 CDC(Table 级别)。
文档里重点讲的是"对表启用 CDC",前置条件:数据库已经开启 CDC、SQL Server Agent 已经运行。
3.1 前置条件检查
- 目标数据库已经开启 CDC;
- SQL Server Agent 正在运行(负责定期扫描日志写 CDC 表);
- 当前账号是该数据库的
db_owner成员。
3.2 为指定表启用 CDC
示例(以 MyDB.dbo.MyTable 为例):
sql
USE MyDB
GO
EXEC sys.sp_cdc_enable_table
@source_schema = N'dbo', -- 源表 schema
@source_name = N'MyTable', -- 源表名
@role_name = N'MyRole', -- 有权限查询 CDC 表的角色
@filegroup_name = N'MyDB_CT', -- CDC 变更表所在文件组(建议单独文件组)
@supports_net_changes = 0 -- 是否支持"净变更"查询,这里先关
GO
几点说明:
@role_name:可以建一个MyRole角色,把需要访问 CDC 变更表的用户加入进来;@filegroup_name:CDC 会为每个表创建对应的 change table,建议放到单独 filegroup,避免和业务表混在一起;@supports_net_changes:是否开启 CDC 的"净变更"功能(即汇总一段时间内的最终变化),通常 Flink CDC 不需要。
3.3 验证 CDC 配置与权限
使用 sys.sp_cdc_help_change_data_capture 查看当前库的 CDC 表配置:
sql
USE MyDB;
GO
EXEC sys.sp_cdc_help_change_data_capture;
GO
返回结果包含:
- 哪些表启用了 CDC;
- 每个表对应的 CDC 表名(capture instance);
- 当前用户是否有权限访问。
如果结果为空,说明:
- 要么当前库根本没启用 CDC;
- 要么当前用户没有权限访问 CDC 表。
四、在 Flink SQL 中创建 SQLServer CDC 表
CDC 源表在 Flink 里就是一张普通的表,DDL 例如:
sql
CREATE TABLE orders (
id INT,
order_date DATE,
purchaser INT,
quantity INT,
product_id INT,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'sqlserver-cdc',
'hostname' = 'localhost',
'port' = '1433',
'username' = 'sa',
'password' = 'Password!',
'database-name'= 'inventory',
'table-name' = 'dbo.orders'
);
-- 直接查询快照 + CDC 变更
SELECT * FROM orders;
注意 table-name 的格式是:schema.table,例如 dbo.orders。
五、核心 Connector 参数说明
5.1 基础连接参数
| 参数名 | 必填 | 说明 |
|---|---|---|
connector |
是 | 固定为 sqlserver-cdc |
hostname |
是 | SQL Server 主机名 / IP |
port |
否 | 默认 1433 |
username |
是 | 登录用户名 |
password |
是 | 登录密码 |
database-name |
是 | 要监控的数据库名 |
table-name |
是 | 要监控的表名,如 dbo.orders |
server-time-zone |
否 | 会话时区,默认 UTC,如需要可设为 Asia/Shanghai |
5.2 增量快照与 chunk 相关参数
SQLServer CDC 也支持增量快照机制,这意味着可以并行读取快照数据,然后切换到 CDC 流:
-
scan.incremental.snapshot.enabled:是否启用增量快照(默认true) -
scan.incremental.snapshot.chunk.key-column:快照切片字段- 默认是主键第一列;
- 可以指定非主键列,但有潜在一致性问题(后面专门讲)。
-
chunk-meta.group.size:chunk 元信息分组大小,默认 1000 -
chunk-key.even-distribution.factor.lower-bound/upper-bound:- 用于判断 chunk key 分布是否"足够均匀",进而决定是否按范围均匀切块;
- 分布因子计算公式大致为:
distributionFactor=MAX(id)−MIN(id)+1rowCount\]\[ distributionFactor = \\frac{MAX(id) - MIN(id) + 1}{rowCount} \]\[distributionFactor=rowCountMAX(id)−MIN(id)+1
如果列值分布均匀,Flink 可以用简单的范围切分;否则需要多跑几次查询做动态切分。
5.3 Debezium 参数透传
通过 debezium.* 可以把参数传给 Debezium SQLServer Connector,例如:
sql
'debezium.snapshot.mode' = 'initial_only'
注意:
scan.startup.mode的实现依赖 Debezium 的snapshot.mode,不要 在 DDL 里同时配置两者,否则可能导致scan.startup.mode不生效。
5.4 其他常用参数
-
scan.incremental.close-idle-reader.enabled:快照结束后是否关闭空闲 reader(默认false); -
对于 Checkpoint 行为还有 Flink 版本约束:
- Flink ≥ 1.14,才支持
execution.checkpointing.checkpoints-after-tasks-finish.enabled; - Flink ≥ 1.15,该配置默认就是
true。
- Flink ≥ 1.14,才支持
六、启动模式:scan.startup.mode
scan.startup.mode 控制 Flink SQLServer CDC 的启动行为,目前支持两种:
-
initial(默认)- 扫描表结构 + 全量快照数据;
- 适用于:需要让下游拥有完整历史数据。
-
latest-offset- 只扫描表结构,不读历史数据;
- 适用于:只关心"从现在开始"的变更。
例如:
sql
CREATE TABLE orders (...) WITH (
'connector' = 'sqlserver-cdc',
...
'scan.startup.mode'= 'latest-offset'
);
再强调一次:
scan.startup.mode底层用的是 Debezium 的snapshot.mode,不要和debezium.snapshot.mode同时配置。
七、一个重要限制:快照阶段无法做 checkpoint
与 Oracle CDC 类似,SQLServer CDC 在扫描快照阶段有一个限制:
在快照扫描期间缺少可恢复位点,源算子无法真正完成 Checkpoint。
Flink 的实际行为是:
- checkpoint 会一直等到超时;
- 超时后将该 checkpoint 标记为失败;
- 如果没做额外配置,Flink 会认为作业失败,从而触发 failover。
对于大表快照,官方建议调高容忍度:
yaml
execution.checkpointing.interval: 10min
execution.checkpointing.tolerable-failed-checkpoints: 100
restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 2147483647
含义:
- 放宽 checkpoint 间隔;
- 允许大量失败的 checkpoint;
- 使用 fixed-delay 重启策略,并把重启次数设得非常大。
八、DataStream API 使用方式
SQLServer CDC 既可以通过 Flink SQL 使用,也可以在 DataStream 中直接作为 Source。
8.1 SourceFunction 单线程模式
最经典的写法,只能单并行读取:
java
SourceFunction<String> sourceFunction = SqlServerSource.<String>builder()
.hostname("localhost")
.port(1433)
.database("inventory") // 监控的数据库
.tableList("dbo.products") // 监控的表
.username("sa")
.password("Password!")
.deserializer(new JsonDebeziumDeserializationSchema()) // Debezium Record -> JSON String
.build();
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.addSource(sourceFunction)
.print()
.setParallelism(1); // 保证顺序
env.execute("SqlServer CDC Source Example");
文档提到:SQLServer CDC 源在变更流阶段只能单线程读取,也就是说真正的 CDC 流只能由一个任务消费,避免乱序。
8.2 增量 CDC Source(2.4.0 之后)
从 Flink CDC 2.4.0 开始提供了一个 增量 CDC Source,支持快照阶段并行:
java
SqlServerIncrementalSource<String> sqlServerSource =
new SqlServerSourceBuilder()
.hostname("localhost")
.port(1433)
.databaseList("inventory")
.tableList("dbo.products")
.username("sa")
.password("Password!")
.deserializer(new JsonDebeziumDeserializationSchema())
.startupOptions(StartupOptions.initial())
.build();
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(3000);
env.fromSource(
sqlServerSource,
WatermarkStrategy.noWatermarks(),
"SqlServerIncrementalSource")
.setParallelism(2) // 快照阶段并行度
.print()
.setParallelism(1);
env.execute("Print SqlServer Snapshot + Change Stream");
简单理解:
- 快照阶段可以并行读取,提高首发同步速度;
- 切换到变更流后仍然由单任务读取 CDC 事件,保证顺序。
九、无主键表 & chunk key 风险
从 3.4.0 开始,SQLServer CDC 支持无主键表,用法与 MySQL / Oracle CDC 一致:
-
必须配置:
sql'scan.incremental.snapshot.chunk.key-column' = 'your_column' -
最好选择:
- 非空字段;
- 有索引;
- 尽量不会被更新。
语义上:
- 如果该列不会被更新 → 仍可以保持 Exactly-Once;
- 如果该列会被更新 → 只能保证 At-Least-Once,需要下游按业务主键做幂等处理。
9.1 官方警告场景
-
表结构:
- 主键:
id - chunk key:
pid(非主键)
- 主键:
-
切片:
- Split 0:
1 < pid <= 3 - Split 1:
3 < pid <= 5
- Split 0:
-
两个并行 task 正在分别读取 Split 0 和 Split 1;
-
此时有一条记录:
id=0的pid从2 → 4; -
结果:
- Split 0 缓存
[id=0, pid=2] - Split 1 缓存
[id=0, pid=4]
- Split 0 缓存
由于处理顺序不可控,最终下游看到的 pid 可能是 2,也可能是 4,产生不一致。
结论:
优先使用主键作为 chunk key;
无主键表或非主键 chunk key 时,要谨慎评估"一致性 vs 性能"。
十、元数据列与 CDC 指标
10.1 可用元数据列(VIRTUAL)
可以通过 VIRTUAL 元数据列,拿到各种"来源信息":
table_name:表名schema_name:schema 名database_name:数据库名op_ts:变更发生时间(快照读出来的记录该值为 0)
示例:
sql
CREATE TABLE products (
table_name STRING METADATA FROM 'table_name' VIRTUAL,
schema_name STRING METADATA FROM 'schema_name' VIRTUAL,
db_name STRING METADATA FROM 'database_name'VIRTUAL,
operation_ts TIMESTAMP_LTZ(3) METADATA FROM 'op_ts' VIRTUAL,
id INT NOT NULL,
name STRING,
description STRING,
weight DECIMAL(10,3)
) WITH (
'connector' = 'sqlserver-cdc',
'hostname' = 'localhost',
'port' = '1433',
'username' = 'sa',
'password' = 'Password!',
'database-name'= 'inventory',
'table-name' = 'dbo.products'
);
10.2 Source Metrics
SQLServer CDC 按 database.schema.table 维度暴露指标,例如:
isSnapshotting:当前是否在做快照;isStreamReading:是否在读变更流;numTablesSnapshotted/numTablesRemaining:多表快照进度;numSnapshotSplitsProcessed/Remaining/Finished:快照分片处理进度;snapshotStartTime/snapshotEndTime:快照开始/结束时间。
这些指标可以接入 Prometheus + Grafana,做一套 CDC 作业监控看板,观察:
- 首发同步进度;
- CDC 延迟;
- 各表健康状态。
十一、类型映射:SQL Server → Flink SQL
Flink 已经帮我们预先做好了 SQLServer 到 Flink SQL 的类型映射,这里简单列一下主流类型:
| SQLServer 类型 | Flink SQL 类型 |
|---|---|
char(n) |
CHAR(n) |
varchar(n) / nvarchar(n) / nchar(n) |
VARCHAR(n) |
text / ntext / xml |
STRING |
decimal(p, s) / money / smallmoney |
DECIMAL(p, s) |
numeric |
NUMERIC |
float / real |
DOUBLE |
bit |
BOOLEAN |
int |
INT |
tinyint / smallint |
SMALLINT |
bigint |
BIGINT |
date |
DATE |
time(n) |
TIME(n) |
datetime / smalldatetime / datetime2 |
TIMESTAMP(n) |
datetimeoffset |
TIMESTAMP_LTZ(3) |
有些"超长精度"的 decimal / money 在 Flink 侧会按 DECIMAL(p, s) 或 NUMERIC 处理,注意保持 p、s 一致,以免精度丢失。
十二、总结:在 .NET / SQL Server 生态里用好 Flink CDC
对于传统的 SQL Server 核心系统,Flink SQLServer CDC Connector 提供了一条非常稳妥的实时化路径:
- 不侵入业务代码;
- 利用 SQL Server 原生 CDC 机制;
- 在 Flink 侧统一使用 SQL / DataStream 做实时计算;
- 可以很自然地把数据推送到 Kafka、Doris、StarRocks、Iceberg、ES 等下游。
落地实践建议:
- 从一两张关键表(订单、库存)试点,先验证 CDC 配置、Flink 作业稳定性;
- 对大表快照提前调好 checkpoint 策略,避免首发同步阶段频繁 failover;
- 充分利用增量快照能力,加快初次同步速度;
- 针对无主键表、chunk key、顺序一致性这些地方,提前设计好下游幂等策略;
- 接入 CDC Metrics 做"可观测",把 CDC 作业当基础设施来运维。