资讯详情

ScyllaDB CDC 基本操作全解:插入、更新与四类删除在 CDC 日志表中的记录规则

📅 2026/9/14 18:47:43 | 华诺云谱 👁 阅读
ScyllaDB CDC 基本操作全解:插入、更新与四类删除在 CDC 日志表中的记录规则
ScyllaDB CDC 基本操作全解插入、更新与四类删除在 CDC 日志表中的记录规则【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb本文以 cdc-basic-operations.rst 为骨架系统讲解 ScyllaDB 变更数据捕获Change Data Capture, CDC日志表如何记录基表上的五类基本操作——插入、更新、单行删除、行范围删除和分区删除。读完本文你将掌握每类操作在*_scylla_cdc_log表中产生的日志行格式、cdc$operation操作码的完整取值含义、cdc$deleted_前缀列的判空机制并能结合 cdc/log.hh 与 cdc/change_visitor.hh 中的源码印证这些规则背后的实现原理。CDC 日志表与基表操作总览CDC 日志表表名_scylla_cdc_log反映的是在基表上实际执行的操作。不同类型的操作会在日志表中产生不同形态的日志行基本操作共五类插入inserts更新updates单行删除single row deletions行范围删除row range deletions分区删除partition deletions一个重要的边界事实TTL 过期不算操作整行因 TTL 过期而消失不会被反映到 CDC 日志表中。如果业务确实需要整行过期这类 CDC 事件文档建议改用 per-row TTL逐行 TTL它会在行过期时生成专门的 CDC 事件。原文档的五节内容分别位于 cdc-updates.rst、cdc-inserts.rst、cdc-row-deletions.rst、cdc-row-range-deletions.rst 和 cdc-partition-deletions.rst下文逐节展开。更新Updatescdc$operation 1与cdc$deleted_前缀列更新是最基本的语句。完整实验步骤如下CREATE TABLE ks.t (pk int, ck int, v1 int, v2 int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; UPDATE ks.t SET v1 0 WHERE pk 0 AND ck 0; UPDATE ks.t SET v2 null WHERE pk 0 AND ck 0;确认基表内容SELECT * FROM ks.t;返回pk | ck | v1 | v2 ------------------ 0 | 0 | 0 | null (1 rows)再查看 CDC 日志表SELECT cdc$batch_seq_no, pk, ck, v1, cdc$deleted_v1, v2, cdc$deleted_v2, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | v1 | cdc$deleted_v1 | v2 | cdc$deleted_v2 | cdc$operation ------------------------------------------------------------------------------------- 0 | 0 | 0 | 0 | null | null | null | 1 0 | 0 | 0 | null | null | null | True | 1 (2 rows)规则要点对应更新的日志行以cdc$operation 1标识主键列在日志表中的取值与基表一致本例为pk 0、ck 0每个非主键列X在日志表中都有同名列同时还有一个cdc$deleted_X辅助列专门标记该列在这次更新中是否被设置为null若更新把X设为非空值日志中的X列即为该值若更新把X设为null日志中的X列为null并通过cdc$deleted_X True表明这是一次置空更新。上例中v2正是这种情况。列删除是更新的一种列删除column deletion在逻辑上等同于把列置为 null 的更新以下两条语句等价UPDATE table_name SET X null WHERE condition; DELETE X FROM table_name WHERE condition;所以上面第二条语句也可以写成DELETE v2 FROM ks.t WHERE pk 0 AND ck 0;得到完全相同的结果。但要特别区分列删除不等于行删除。行删除row deletion是指指定了完整聚类键、但不指定任何具体列的DELETE语句DELETE FROM ks.t WHERE pk 0 AND ck 0;行删除的日志形态见下文行删除一节。插曲静态行static row在 ScyllaDB 中的行为如果一张表含有静态列static那么每个分区都包含一条分区内全局的静态行。它不同于聚类行静态行只承载分区键列与静态列的值。示例CREATE TABLE ks.t (pk int, ck int, s int static, c int, PRIMARY KEY (pk, ck)); UPDATE ks.t SET s 0 WHERE pk 0; SELECT * from ks.t WHERE pk 0;返回pk | ck | s | c ------------------- 0 | null | 0 | null (1 rows)注意虽然没有设置任何常规列查询仍返回了一行——那就是静态行。继续更新聚类行UPDATE ks.t SET c 0 WHERE pk 1 AND ck 0; SELECT * from ks.t WHERE pk 1;返回pk | ck | s | c ----------------- 1 | 0 | null | 0 (1 rows)CQL 在静态行与聚类行同处一个分区时会把它们混排展示容易造成误解UPDATE ks.t SET c 0 WHERE pk 2 AND ck 0; UPDATE ks.t SET c 1 WHERE pk 2 AND ck 1; UPDATE ks.t SET s 2 WHERE pk 2; SELECT * from ks.t WHERE pk 2;返回pk | ck | s | c -------------- 2 | 0 | 2 | 0 2 | 1 | 2 | 1 (2 rows)从结果看似乎静态列长在了每一行聚类行上。事实并非如此这只是 CQL 在静态行与聚类行同时存在时的展示方式——它把静态行混合进了每个聚类行的输出。理解静态行的正确模型是它是分区内一条独立的行且一个分区至多存在一条静态行当且仅当至少一个静态列非空时静态行存在。CDC 如何拆分静态行更新与聚类行更新CDC 把静态行更新与聚类行更新拆开成不同的日志条目即使你在一条语句里同时更新了两者CREATE TABLE ks.t (pk int, ck int, s int static, c int, PRIMARY KEY (pk, ck)) WITH cdc {enabled: true}; UPDATE ks.t SET s 0, c 0 WHERE pk 0 AND ck 0; SELECT cdc$batch_seq_no, pk, ck, s, c, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | s | c | cdc$operation ------------------------------------------------------- 0 | 0 | null | 0 | null | 1 1 | 0 | 0 | null | 0 | 1 (2 rows)CDC 在逻辑上识别出发生了两次更新一次针对静态行、一次针对聚类行等价于BEGIN UNLOGGED BATCH UPDATE ks.t SET s 0 WHERE pk 0; UPDATE ks.t SET c 0 WHERE pk 0 AND ck 0; APPLY BATCH;由于它们来自同一条语句、共享同一个时间戳日志表用cdc$batch_seq_no把它们归为一组同批次序号递增区分。区分两者时看聚类键列本例的ck静态行更新中聚类键为null聚类行更新中非null。插入Insertscdc$operation 2与 row marker插入与更新的本质区别row markerCassandra/ScyllaDB 社区中一个普遍的误解是插入和更新差不多。实际上二者并不相同。示例一CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; UPDATE ks.t SET v null WHERE pk 0 AND ck 0; SELECT * FROM ks.t WHERE pk 0 AND ck 0;返回pk | ck | v ----------- (0 rows)而换成INSERTINSERT INTO ks.t (pk,ck,v) VALUES (0, 0, null); SELECT * FROM ks.t WHERE pk 0 AND ck 0;返回pk | ck | v -------------- 0 | 0 | null (1 rows)原理每张表都有一个不可见的额外列称为row marker行标记。它不保存数据值只保存存活信息时间戳与 TTL。只要 row marker 存活即使所有非主键列都是null查询时该行依然可见。区别在于UPDATE 不影响 row marker而 INSERT 会创建一个存活的 row marker。再看一个对照实验CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; UPDATE ks.t SET v 0 WHERE pk 0 AND ck 0; SELECT * FROM ks.t;返回pk | ck | v ----------- 0 | 0 | 0 (1 rows)此时(pk 0, ck 0)行之所以可见是因为v列的值撑着它。把它删掉行就消失了UPDATE ks.t SET v null WHERE pk 0 AND ck 0; SELECT * FROM ks.t;返回pk | ck | v ----------- (0 rows)但如果一开始用的是INSERT删除v之后行仍然可见INSERT INTO ks.t (pk, ck, v) VALUES (0, 0, 0); UPDATE ks.t set v null where pk 0 and ck 0; SELECT * from ks.t;返回pk | ck | v -------------- 0 | 0 | null (1 rows)因为INSERT引入的 row marker 让该行保持存活。还可以只创建 row marker 而不设置任何列INSERT INTO ks.t (pk, ck) VALUES (0, 0);因此一条同时指定键列与非键列的INSERT实际是两件事的合并——创建 row marker设置单元格INSERT INTO ks.t (pk, ck, v) VALUES (0, 0, 0);等价于BEGIN UNLOGGED BATCH INSERT INTO ks.t (pk, ck) VALUES (0, 0); UPDATE ks.t SET v 0 WHERE pk 0 AND ck 0; APPLY BATCH;插入在 CDC 日志中的形态不涉及集合列与静态列时插入对 CDC 日志的影响与更新几乎相同差别只在cdc$operation列CREATE TABLE ks.t (pk int, ck int, v1 int, v2 int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; INSERT INTO ks.t (pk, ck, v1) VALUES (0, 0, 0); INSERT INTO ks.t (pk, ck, v2) VALUES (0, 0, NULL);确认基表SELECT * FROM ks.t;返回pk | ck | v1 | v2 ------------------ 0 | 0 | 0 | null (1 rows)查看日志表SELECT cdc$batch_seq_no, pk, ck, v1, cdc$deleted_v1, v2, cdc$deleted_v2, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | v1 | cdc$deleted_v1 | v2 | cdc$deleted_v2 | cdc$operation ------------------------------------------------------------------------------------- 0 | 0 | 0 | 0 | null | null | null | 2 0 | 0 | 0 | null | null | null | True | 2 (2 rows)对应插入的日志行以cdc$operation 2标识cdc$deleted_前缀列的语义与更新部分完全一致。INSERT 中静态行的分离如果INSERT同时写入了静态列CDC 会把静态行更新从INSERT中分离出来CREATE TABLE ks.t (pk int, ck int, s int static, c int, PRIMARY KEY (pk, ck)) WITH cdc {enabled: true}; INSERT INTO ks.t (pk, ck, s, c) VALUES (0, 0, 0, 0); SELECT cdc$batch_seq_no, pk, ck, s, c, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | s | c | cdc$operation ------------------------------------------------------- 0 | 0 | null | 0 | null | 1 1 | 0 | 0 | null | 0 | 2 (2 rows)不存在所谓静态行插入静态行没有 row marker让静态行出现的唯一途径是把某个静态列设为非空值。因此以下语句INSERT INTO ks.t (pk, s) VALUES (0, 0);等价于UPDATE ks.t SET s 0 WHERE pk 0;这也是上例中静态行更新的cdc$operation为1update而非2insert的原因。行删除Row deletions整行删除行删除指用DELETE语句删除一整条聚类行给出全部聚类键列的取值但不指定任何具体常规列。完整实验CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH cdc {enabled: true}; INSERT INTO ks.t (pk, ck, v) VALUES (0,0,0);查看基表SELECT * from ks.t;返回pk | ck | v ----------- 0 | 0 | 0 (1 rows)执行行删除DELETE FROM ks.t WHERE pk 0 AND ck 0;删除后再查该行应返回 0 行SELECT * from ks.t WHERE pk 0 AND ck 0;返回pk | ck | v -------------- (0 rows)再强调一次边界行删除不同于列删除。列删除是DELETE语句中指定了某个具体非主键列例如DELETE v FROM ks.t WHERE pk 0 AND ck 0;列删除等价于把该列置 null 的更新UPDATE ks.t SET v null WHERE pk 0 AND ck 0;从源码枚举 cdc/log.hh#L59-L68 可以看到行删除对应操作码row_delete 3即行删除在日志表中的cdc$operation取值为3原文档示例未展示该行删除的日志输出该取值以源码枚举为准。行范围删除Row range deletionscdc$operation 5~8范围删除指删除某个分区内、由连续聚类键区间指定的一组行。它只作用于单个分区因此执行范围删除时必须给定唯一的分区键。单列聚类键示例CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; INSERT INTO ks.t (pk,ck,v) VALUES (0,0,0); INSERT INTO ks.t (pk,ck,v) VALUES (0,1,1); INSERT INTO ks.t (pk,ck,v) VALUES (0,2,2); INSERT INTO ks.t (pk,ck,v) VALUES (0,3,3);查看基表SELECT * FROM ks.t;返回pk | ck | v ----------- 0 | 0 | 0 0 | 1 | 1 0 | 2 | 2 0 | 3 | 3 (4 rows)删除pk 0且ck落在(0, 2]左开右闭区间的行DELETE FROM ks.t WHERE pk 0 AND ck 2 and ck 0;删除后基表SELECT * FROM ks.t;返回pk | ck | v ----------- 0 | 0 | 0 0 | 3 | 3 (2 rows)查看日志表SELECT cdc$batch_seq_no, pk, ck, v, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | v | cdc$operation ----------------------------------------------- 0 | 0 | 0 | 0 | 2 0 | 0 | 1 | 1 | 2 0 | 0 | 2 | 2 | 2 0 | 0 | 3 | 3 | 2 0 | 0 | 0 | null | 6 1 | 0 | 2 | null | 7 (6 rows)规则一次范围删除在 CDC 日志中表现为至多两条日志行——一条对应区间左端点如果指定了左端点一条对应右端点如果指定了右端点。对应的cdc$operation取值5包含左端点inclusive left bound6不包含左端点exclusive left bound7包含右端点inclusive right bound8不包含右端点exclusive right bound上例区间为(0, 2]左开右闭因此日志呈现为两条cdc$operation 6左端点ck 0不含与cdc$operation 7右端点ck 2含。这些条目中非主键列的值一律为null。单边区间如果只指定单边例如DELETE FROM ks.t WHERE pk 0 AND ck 3;CDC 日志中同样会出现两条一条cdc$operation 5且聚类键为null表示区间没有左边界另一条cdc$operation 8表示右端点cdc$batch_seq_no | pk | ck | v | cdc$operation ------------------------------------------------- 0 | 0 | null | null | 5 1 | 0 | 3 | null | 8多列聚类键的范围删除当聚类键由多列组成时范围删除允许对聚类键的某个前缀指定等值关系紧接着对前缀之后的那一列指定排序关系。示例CREATE TABLE ks.t (pk int, ck1 int, ck2 int, ck3 int, v int, primary key (pk, ck1, ck2, ck3)) WITH cdc {enabled:true};删除pk 0、前缀ck1 0、且ck2落在(0, 3)区间的行DELETE FROM ks.t WHERE pk 0 and ck1 0 AND ck2 0 AND ck2 3;查看日志表SELECT cdc$batch_seq_no, pk, ck1, ck2, ck3, v, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck1 | ck2 | ck3 | v | cdc$operation ----------------------------------------------------------- 0 | 0 | 0 | 0 | null | null | 6 1 | 0 | 0 | 3 | null | null | 8 (2 rows)可以看到等值前缀ck1 0直接体现在日志行的主键列中开区间(0, 3)则分别落成cdc$operation 6不含左端点ck2 0和cdc$operation 8不含右端点ck2 3两条日志行区间之外的剩余聚类键列ck3为null。分区删除Partition deletionscdc$operation 4删除整个分区的示例CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH cdc {enabled:true}; INSERT INTO ks.t (pk,ck,v) VALUES (0,0,0); INSERT INTO ks.t (pk,ck,v) VALUES (0,1,1);查看基表SELECT * FROM ks.t;返回pk | ck | v ----------- 0 | 0 | 0 0 | 1 | 1 (2 rows)删除pk 0对应的全部分区行DELETE FROM ks.t WHERE pk 0;查看 CDC 日志表SELECT cdc$batch_seq_no, pk, ck, v, cdc$operation FROM ks.t_scylla_cdc_log;返回cdc$batch_seq_no | pk | ck | v | cdc$operation ----------------------------------------------- 0 | 0 | 0 | 0 | 2 0 | 0 | 1 | 1 | 2 0 | 0 | null | null | 4 (3 rows)规则分区删除在日志表中由cdc$operation 4标识日志行中对应聚类键与非主键列的列值全部为null。源码印证cdc::operation枚举与 ChangeVisitor文档中的所有cdc$operation取值都能在源码中找到直接依据。cdc/log.hh 中定义了完整的操作码枚举// cdc/log.hh, 约 L59-L68 enum class operation : int8_t { // note: these values will eventually be read by a third party, probably not privvy to this // enum decl, so dont change the constant values (or the datatype). pre_image 0, update 1, insert 2, row_delete 3, partition_delete 4, range_delete_start_inclusive 5, range_delete_start_exclusive 6, range_delete_end_inclusive 7, range_delete_end_exclusive 8, post_image 9, // Operations initiated internally by Scylla. Currently used only by Alternator service_row_delete -3, service_partition_delete -4, };源码注释特别说明这些常量值会被第三方消费因此数值和类型都不允许改动——这也解释了为什么 CDC 日志表的操作码必须保持稳定。对照可见本文档覆盖的 1~8 全部落地而0pre-image前置镜像与9post-image后置镜像属于 CDC 高级特性镜像模式范畴负值操作码则仅由 Scylla 内部发起目前仅 Alternator 使用。这些操作码是如何被写进日志表的从源码结构看核心路径是 cdc/change_visitor.hh 定义的ChangeVisitor抽象它把一个 mutation 解构成原子变更件并逐一消费文档头注释列出的原子件与 CQL 操作一一对应row marker对应 insert 的存活信息→ 日志中区分 update(1) 与 insert(2) 的关键row tombstoneclustered_row_delete→ 行删除3range tombstonerange_delete→ 范围删除的四类端点5~8partition tombstonepartition_delete→ 分区删除4行单元格变更clustered_row_cells/static_row_cells→ 更新1其中死单元格dead_atomic_cell即置空更新对应cdc$deleted_前缀列置True。inspect_mutation的遍历顺序cdc/change_visitor.hh#L204-L269也印证了文档中静态行更新与聚类行更新被拆分为独立日志条目的行为静态行先被独立访问随后逐行访问聚类行的 marker、单元格与行 tombstone最后才访问 range tombstone 与 partition tombstone。此外cdc/log.cc 中的cdc_service::impl作为 migration listener 负责日志表的生命周期on_pre_create_column_families在基表创建时同步生成日志表 schema并施加文档中未展开的两项约束——不支持计数器列ensure_that_table_has_no_counter_columns以及在部分集群尚不支持 tablet 的 CDC 特性时要求 keyspace 使用 vnodeensure_that_table_uses_vnodes。这说明启用 CDC 的表在拓扑上存在适用前提实验前应确认集群能力。cdc$operation操作码速查表综合原文档示例与 cdc/log.hh#L59-L68 源码枚举完整映射如下值含义本文档覆盖情况0pre-image前置镜像镜像模式高级特性见 CDC preimage 相关文档1update更新含列删除与静态行更新本文更新一节2insert插入创建 row marker本文插入一节3row_delete单行删除本文行删除一节4partition_delete分区删除本文分区删除一节5range_delete_start_inclusive范围左端点含本文行范围删除一节6range_delete_start_exclusive范围左端点不含同上7range_delete_end_inclusive范围右端点含同上8range_delete_end_exclusive范围右端点不含同上9post-image后置镜像高级特性-3 / -4service_row_delete / service_partition_delete内部发起目前仅 Alternator 使用内部操作非用户 CQL 语句产生小结ScyllaDB 的 CDC 日志表以cdc$operation操作码为核心索引把基表上的写入语义无损地映射为可查询的日志行更新与插入1/2复用同一套值列 cdc$deleted_置空标记列的 delta 结构靠 row marker 的有无区分行删除3、分区删除4与范围删除5~8 的区间端点编码则各自对应 tombstone 的不同粒度。消费 CDC 日志时先按cdc$batch_seq_no归组同语句产生的多个条目再按cdc$operation分派处理逻辑即可完整重放基表变更同时需注意 TTL 过期不产生日志条目这一边界必要时改用 per-row TTL 方案。【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。