Skip to content

Commit 228b322

Browse files
authored
ticdc: add description for cdc behaviour change (#17363)
1 parent 6b16384 commit 228b322

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

releases/release-8.1.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ TiDB 8.1.0 为长期支持版本 (Long-Term Support Release, LTS)。
172172

173173
* 在之前的版本中,TiDB Lightning 的配置项 `tidb.tls` 在取值为 `"false"``""` 时的行为是相同的,在取值为 `"skip-verify"``"preferred"` 时的行为也是相同的。从 v8.1.0 开始,TiDB Lightning 对 `tidb.tls` 取值为 `"false"``""``"skip-verify"``"preferred"` 时的行为进行了区分。更多信息,请参考 [TiDB Lightning 配置参数](/tidb-lightning/tidb-lightning-configuration.md)
174174
* 对于设置了 `AUTO_ID_CACHE=1` 的表,TiDB 支持[中心化分配自增 ID 服务](/auto-increment.md#mysql-兼容模式)。在之前的版本中,该服务的“主”TiDB 节点在进程退出(如该 TiDB 节点重启)时会自动执行 `forceRebase` 操作,以确保自动分配的 ID 尽可能连续。然而,当设置过 `AUTO_ID_CACHE=1` 的表过多时,执行 `forceRebase` 会非常耗时,导致 TiDB 无法及时重启,甚至阻塞数据写入,影响系统可用性。因此,从 v8.1.0 起,TiDB 取消了 `forceRebase` 操作,解决了上述问题,但会造成主备切换期间部分自动分配的 ID 出现不连续。
175+
* 在之前的版本中,TiCDC 在处理包含 `UPDATE` 变更的事务时,如果事件的主键或者非空唯一索引的列值发生改变,则会将该条事件拆分为 `DELETE``INSERT` 两条事件。从 v8.1.0 开始,当使用 MySQL Sink 时,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,且事务的 `commitTS` 小于 TiCDC 启动时从 PD 获取的当前时间戳 `thresholdTs`,TiCDC 才会将该 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件,然后写入 Sorter 模块。该行为变更解决了由于 TiCDC 接收到的 `UPDATE` 事件顺序可能不正确,导致拆分后的 `DELETE``INSERT` 事件顺序也可能不正确,从而引发下游数据不一致的问题。更多信息,请参考[用户文档](/ticdc/ticdc-behavior-change.md#mysql-sink)
175176

176177
### 系统变量
177178

ticdc/ticdc-behavior-change.md

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ summary: 介绍 TiCDC changefeed 的行为变更,说明变更原因以及影
55

66
# TiCDC 行为变更说明
77

8-
## Update 事件拆分为 DeleteInsert 事件
8+
## `UPDATE` 事件拆分为 `DELETE``INSERT` 事件
99

10-
### 含有单条 Update 变更的事务拆分
10+
### 含有单条 `UPDATE` 变更的事务拆分
1111

12-
从 v6.5.3、v7.1.1 和 v7.2.0 开始,使用非 MySQL Sink 时,对于仅包含一条 Update 变更的事务,如果 Update 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该条事件拆分为 DeleteInsert 两条事件。详情见 GitHub issue [#9086](https://github.com/pingcap/tiflow/issues/9086)
12+
从 v6.5.3、v7.1.1 和 v7.2.0 开始,使用非 MySQL Sink 时,对于仅包含一条 `UPDATE` 变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该条事件拆分为 `DELETE``INSERT` 两条事件。详情见 GitHub issue [#9086](https://github.com/pingcap/tiflow/issues/9086)
1313

1414
该变更主要为了解决如下问题:
1515

@@ -24,14 +24,14 @@ INSERT INTO t VALUES (1, 1);
2424
UPDATE t SET a = 2 WHERE a = 1;
2525
```
2626

27-
在上述示例中,主键 `a` 的值从 `1` 修改为 `2`。如果不将该 Update 事件进行拆分:
27+
在上述示例中,主键 `a` 的值从 `1` 修改为 `2`。如果不将该 `UPDATE` 事件进行拆分:
2828

2929
* 在使用 CSV 和 AVRO 协议时,消费者仅能看到新值 `a = 2`,而无法得到旧值 `a = 1`。这可能导致下游消费者只插入了新值 `2`,而没有删除旧值 `1`
30-
* 在使用 Index value dispatcher 时,插入记录 `(1, 1)` 的事件可能被发送到 Partition 0,而变更事件 `(2, 1)` 可能被发送到 Partition 1。如果 Partition 1 的消费进度快于 Partition 0,则可能由于下游数据系统中找不到相应数据而导致出错。因此,TiCDC 会将该条 Update 事件拆分为 DeleteInsert 两条事件,其中,删除记录 `(1, 1)` 被发送到 Partition 0,写入记录 `(2, 1)` 被发送到 Partition 1,以确保无论消费者的进度如何,事件都能被消费成功。
30+
* 在使用 Index value dispatcher 时,插入记录 `(1, 1)` 的事件可能被发送到 Partition 0,而变更事件 `(2, 1)` 可能被发送到 Partition 1。如果 Partition 1 的消费进度快于 Partition 0,则可能由于下游数据系统中找不到相应数据而导致出错。因此,TiCDC 会将该条 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件,其中,删除记录 `(1, 1)` 被发送到 Partition 0,写入记录 `(2, 1)` 被发送到 Partition 1,以确保无论消费者的进度如何,事件都能被消费成功。
3131

32-
### 含有多条 Update 变更的事务拆分
32+
### 含有多条 `UPDATE` 变更的事务拆分
3333

34-
从 v6.5.4、v7.1.2 和 v7.4.0 开始,对于一个含有多条变更的事务,如果 Update 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 DeleteInsert 两条事件,并确保所有事件按照 Delete 事件在 Insert 事件之前的顺序进行排序。详情见 GitHub issue [#9430](https://github.com/pingcap/tiflow/issues/9430)
34+
从 v6.5.4、v7.1.2 和 v7.4.0 开始,对于一个含有多条变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 `DELETE``INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。详情见 GitHub issue [#9430](https://github.com/pingcap/tiflow/issues/9430)
3535

3636
该变更主要为了解决当使用 MySQL Sink 直接将这两条事件写入下游时,可能会出现主键或唯一键冲突的问题,从而导致 changefeed 报错。当使用 Kafka Sink 或其他 Sink 时,如果消费者需要将数据变更写入关系型数据库或进行类似操作,也可能遇到相同问题。
3737

@@ -49,6 +49,69 @@ UPDATE t SET a = 2 WHERE a = 3;
4949
COMMIT;
5050
```
5151

52-
在上述示例中,通过执行三条 SQL 语句对两行数据的主键进行交换,但 TiCDC 只会接收到两条 Update 变更事件,即将主键 `a``1` 变更为 `2`,将主键 `a``2` 变更为 `1`,如果 MYSQL Sink 直接将这两条 Update 事件写入下游,会出现主键冲突的问题,导致 changefeed 报错。
52+
在上述示例中,通过执行三条 SQL 语句对两行数据的主键进行交换,但 TiCDC 只会接收到两条 `UPDATE` 变更事件,即将主键 `a``1` 变更为 `2`,将主键 `a``2` 变更为 `1`,如果 MYSQL Sink 直接将这两条 `UPDATE` 事件写入下游,会出现主键冲突的问题,导致 changefeed 报错。
5353

5454
因此,TiCDC 会将这两条事件拆分为四条事件,即删除记录 `(1, 1)``(2, 2)` 以及写入记录 `(2, 1)``(1, 2)`
55+
56+
### MySQL Sink
57+
58+
从 v8.1.0 开始,当使用 MySQL Sink 时,TiCDC 在启动时会从 PD 获取当前的时间戳 `thresholdTs`,并根据时间戳的值决定是否拆分 `UPDATE` 事件:
59+
60+
- 对于含有单条或多条 `UPDATE` 变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,且事务的 `commitTS` 小于 `thresholdTs`,在写入 Sorter 模块之前 TiCDC 会将每条 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件。
61+
- 对于事务的 `commitTS` 大于或等于 `thresholdTs``UPDATE` 事件,TiCDC 不会对其进行拆分。详情见 GitHub issue [#10918](https://github.com/pingcap/tiflow/issues/10918)
62+
63+
该行为变更解决了由于 TiCDC 接收到的 `UPDATE` 事件顺序可能不正确,导致拆分后的 `DELETE``INSERT` 事件顺序也可能不正确,从而引发下游数据不一致的问题。
64+
65+
以如下 SQL 为例:
66+
67+
```sql
68+
CREATE TABLE t (a INT PRIMARY KEY, b INT);
69+
INSERT INTO t VALUES (1, 1);
70+
INSERT INTO t VALUES (2, 2);
71+
72+
BEGIN;
73+
UPDATE t SET a = 3 WHERE a = 2;
74+
UPDATE t SET a = 2 WHERE a = 1;
75+
COMMIT;
76+
```
77+
78+
在该示例中,事务内的两条 `UPDATE` 语句的执行顺序有先后依赖关系,即先将主键 `a``2` 变更为 `3`,再将主键 `a``1` 变更为 `2`。执行完该事务后,上游数据库内的记录为 `(2, 1)``(3, 2)`
79+
80+
但 TiCDC 内部收到的 `UPDATE` 事件顺序可能与上游事务内部实际的执行顺序不同,例如:
81+
82+
```sql
83+
UPDATE t SET a = 2 WHERE a = 1;
84+
UPDATE t SET a = 3 WHERE a = 2;
85+
```
86+
87+
- 在引入该行为变更之前,TiCDC 会将这些 `UPDATE` 事件写入 Sorter 模块之后再将其拆分为 `DELETE``INSERT` 事件。拆分后下游实际执行的事件顺序如下:
88+
89+
```sql
90+
BEGIN;
91+
DELETE FROM t WHERE a = 1;
92+
REPLACE INTO t VALUES (2, 1);
93+
DELETE FROM t WHERE a = 2;
94+
REPLACE INTO t VALUES (3, 2);
95+
COMMIT;
96+
```
97+
98+
下游执行完该事务后,数据库内的记录为 `(3, 2)`,与上游数据库的记录(即 `(2, 1)``(3, 2)`)不同,即发生了数据不一致问题。
99+
100+
- 在引入该行为变更之后,如果该事务的 `commitTS` 小于 TiCDC 在启动时获取的 `thresholdTs`,TiCDC 会在这些 `UPDATE` 事件写入 Sorter 模块之前将其拆分为 `DELETE``INSERT` 事件,经过 Sorter 排序后下游实际执行的事件顺序如下:
101+
102+
```sql
103+
BEGIN;
104+
DELETE FROM t WHERE a = 1;
105+
DELETE FROM t WHERE a = 2;
106+
REPLACE INTO t VALUES (2, 1);
107+
REPLACE INTO t VALUES (3, 2);
108+
COMMIT;
109+
```
110+
111+
下游执行完该事务后,下游数据库内的记录和上游数据库一样,都为 `(2, 1)``(3, 2)`,保证了数据一致性。
112+
113+
从该示例中可以看到,在写入 Sorter 模块之前将 `UPDATE` 事件拆分为 `DELETE``INSERT` 事件,可以保证拆分后所有的 `DELETE` 事件都在 `INSERT` 事件之前执行,这样无论 TiCDC 收到的 `UPDATE` 事件顺序,均可以保证数据一致性。
114+
115+
> **注意:**
116+
>
117+
> 该行为变更后,在使用 MySQL Sink 时,TiCDC 在大部分情况下都不会拆分 `UPDATE` 事件,因此 changefeed 在运行时可能会出现主键或唯一键冲突的问题。该问题会导致 changefeed 自动重启,重启后发生冲突的 `UPDATE` 事件会被拆分为 `DELETE``INSERT` 事件并写入 Sorter 模块中,此时可以确保同一事务内所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序,从而正确完成数据同步。

0 commit comments

Comments
 (0)