From f0d6fdfc5b383627d712a704c85288ae8bf91e72 Mon Sep 17 00:00:00 2001 From: Chao Wang Date: Wed, 4 Mar 2026 11:03:06 +0800 Subject: [PATCH 1/2] *: Add active-active doc --- TOC.md | 3 + active-active-table.md | 181 ++++++++++++++++++ keywords.md | 5 + pd-configuration-file.md | 14 ++ soft-delete-table.md | 161 ++++++++++++++++ .../sql-statement-alter-database.md | 17 ++ sql-statements/sql-statement-alter-table.md | 4 + .../sql-statement-create-database.md | 21 ++ sql-statements/sql-statement-create-table.md | 19 +- system-variables.md | 34 ++++ ticdc/ticdc-active-active-replication.md | 177 +++++++++++++++++ 11 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 active-active-table.md create mode 100644 soft-delete-table.md create mode 100644 ticdc/ticdc-active-active-replication.md diff --git a/TOC.md b/TOC.md index 79adf0d7ef7f..c8f45b0a4541 100644 --- a/TOC.md +++ b/TOC.md @@ -178,6 +178,9 @@ - [日志过滤器](/ticdc/ticdc-filter.md) - [DDL 同步](/ticdc/ticdc-ddl.md) - [双向复制](/ticdc/ticdc-bidirectional-replication.md) + - [Active-Active 双向同步](/ticdc/ticdc-active-active-replication.md) + - [Active-Active 表](/active-active-table.md) + - [Soft Delete 表](/soft-delete-table.md) - 监控告警 - [基本监控指标](/ticdc/ticdc-summary-monitor.md) - [详细监控指标](/ticdc/monitor-ticdc.md) diff --git a/active-active-table.md b/active-active-table.md new file mode 100644 index 000000000000..e29b603ac59f --- /dev/null +++ b/active-active-table.md @@ -0,0 +1,181 @@ +--- +title: Active-Active 表 +summary: 介绍 Active-Active 表在 TiDB 层面的作用、创建方式、相关系统变量与限制。 +--- + +# Active-Active 表 + +Active-Active 表是 TiDB 为 Active-Active(双活)同步场景提供的表能力。它通过隐藏列记录写入时间戳,并结合软删除(`SOFTDELETE`)机制,为多集群多写场景下的冲突解决(Last Write Wins,LWW)提供基础能力。 + +> **说明:** +> +> 本文档仅介绍 Active-Active 表在 TiDB 层如何创建和使用。如何配置 TiCDC 进行双向同步请参阅相关文档。 + +## 使用前提 + +- 你需要部署多个 TiDB 集群,并在集群间部署 TiCDC 同步链路(用于跨集群同步变更)。 +- 你需要确保各集群的 PD 生成的时间戳在全局范围内可比较且不会冲突。为此,需要为每个集群配置 PD 的 `tso-max-index` 与 `tso-unique-index`(详见 [PD 配置文件](/pd-configuration-file.md#tso-max-index) 和 [PD 配置文件](/pd-configuration-file.md#tso-unique-index))。 +- 建议为各集群配置 NTP 等时间同步机制,避免因时钟漂移导致事务提交失败或等待时间过长。 + +> **注意:** +> +> Active-Active 同步不提供跨集群的全局事务一致性,属于最终一致性方案。对于同一行的并发写入可能产生“丢失更新”等现象,请谨慎评估业务适用性。 + +## 创建 Active-Active 表 + +Active-Active 表通过表选项 `ACTIVE_ACTIVE='ON'` 启用,并且**必须同时启用软删除**(`SOFTDELETE=RETENTION ...`)。`SOFTDELETE` 选项仅支持 `RETENTION ...` 或 `'OFF'`,不支持 `'ON'`。 + +### 通过数据库选项统一启用(推荐) + +你可以在创建数据库时启用 `ACTIVE_ACTIVE` 与 `SOFTDELETE`,该数据库下新创建的表会自动继承这些选项: + +```sql +CREATE DATABASE aa_example ACTIVE_ACTIVE='ON' SOFTDELETE=RETENTION 7 DAY; + +USE aa_example; +CREATE TABLE message ( + id INT PRIMARY KEY, + text VARCHAR(10) +); +``` + +通过 `SHOW CREATE TABLE` 可以看到这些选项会以注释形式展示(用于 MySQL 兼容),例如: + +```sql +SHOW CREATE TABLE message\G +``` + +示例输出如下(内容会包含 `/*T![active_active] ACTIVE_ACTIVE='ON' */`、`/*T![softdelete] SOFTDELETE=RETENTION 7 DAY */` 等片段): + +```text +*************************** 1. row *************************** + Table: message +Create Table: CREATE TABLE `message` ( + `id` int NOT NULL, + `text` varchar(10) DEFAULT NULL, + PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![active_active] ACTIVE_ACTIVE='ON' */ /*T![softdelete] SOFTDELETE=RETENTION 7 DAY */ /*T![softdelete] SOFTDELETE_JOB_ENABLE='ON' */ /*T![softdelete] SOFTDELETE_JOB_INTERVAL='24h' */ +``` + +### 在建表时单独启用 + +你也可以在建表时直接指定选项: + +```sql +CREATE TABLE message ( + id INT PRIMARY KEY, + text VARCHAR(10) +) ACTIVE_ACTIVE='ON' SOFTDELETE=RETENTION 7 DAY; +``` + +### Soft Delete 相关配置 + +Active-Active 表同时也是 Soft Delete 表。如何配置 `SOFTDELETE` 的保留期、后台清理任务开关与执行间隔,请参阅 [Soft Delete 表](/soft-delete-table.md#调整-soft-delete-相关选项)。 + +> **注意:** +> +> Active-Active 表不支持将 `SOFTDELETE` 设置为 `'OFF'`,否则会报错。 + +## 隐藏列与冲突解决(LWW) + +启用 Active-Active 后,TiDB 会为表增加一个隐藏列 `_tidb_origin_ts`,用于记录该行数据在上游集群的原始提交时间戳(TiCDC 写入下游时填充)。当 `_tidb_origin_ts` 为 `NULL` 时表示该行由本地事务写入;当 `_tidb_origin_ts` 不为 `NULL` 时表示该行由上游变更同步而来。 + +同时,TiDB 提供一个只读列 `_tidb_commit_ts` 用于查询该行在本地集群的提交时间戳。`_tidb_commit_ts` 不属于真实表结构,不能用于 `ADD COLUMN`、`ADD INDEX` 等 DDL。 + +在冲突处理时,可以用如下表达式表示一行数据用于 LWW 冲突解决的时间戳: + +```sql +IFNULL(_tidb_origin_ts, _tidb_commit_ts) +``` + +示例: + +```sql +DROP TABLE IF EXISTS message_lww; +CREATE TABLE message_lww ( + id INT PRIMARY KEY, + text VARCHAR(10) +) ACTIVE_ACTIVE='ON' SOFTDELETE=RETENTION 7 DAY; + +INSERT INTO message_lww VALUES (1, 'local'), (2, 'up'); + +-- 为了展示效果,这里通过手动写入 _tidb_origin_ts 来模拟 TiCDC 在下游写入时填充该列。正式环境下不建议修改 _tidb_origin_ts 列,否则可能导致 Active-Active 同步结果不一致。 +UPDATE message_lww SET _tidb_origin_ts=464677399908313272 WHERE id=2; + +SELECT + id, + _tidb_origin_ts, + _tidb_commit_ts, + IFNULL(_tidb_origin_ts, _tidb_commit_ts) AS lww_ts +FROM message_lww +ORDER BY id; +``` + +```text ++----+--------------------+--------------------+--------------------+ +| id | _tidb_origin_ts | _tidb_commit_ts | lww_ts | ++----+--------------------+--------------------+--------------------+ +| 1 | | 464677389206814721 | 464677389206814721 | +| 2 | 464677399908313272 | 464677437959045121 | 464677399908313272 | ++----+--------------------+--------------------+--------------------+ +``` + +- `id=1`:`_tidb_origin_ts` 为 `NULL`,表示该行由本地事务写入,此时 `lww_ts` 取值来自 `_tidb_commit_ts`。 +- `id=2`:`_tidb_origin_ts` 不为 `NULL`,表示该行由上游变更同步而来,此时 `lww_ts` 取值来自 `_tidb_origin_ts`。 + +### 本地写入覆盖上游写入时的行为 + +当你在本地集群对一行“来自上游”的数据执行写入(例如 `UPDATE`)时,TiDB 会把这次写入视为本地写入,并将该行的 `_tidb_origin_ts` 重置为 `NULL`。同时,本地事务的提交时间戳会保证大于该行的“LWW 时间戳”(即更新前的 `IFNULL(_tidb_origin_ts, _tidb_commit_ts)`),以避免旧写入覆盖新写入。若本地 PD 分配的 TSO 落后于该行的“LWW 时间戳”,事务提交可能会短暂等待重试;在时钟漂移较大时,事务也可能失败。 + +示例(继续使用上一节的 `message_lww`): + +```sql +SELECT id, text, _tidb_origin_ts FROM message_lww ORDER BY id; + +UPDATE message_lww SET text='local2' WHERE id=2; + +SELECT id, text, _tidb_origin_ts FROM message_lww ORDER BY id; +``` + +```text ++----+-------+--------------------+ +| id | text | _tidb_origin_ts | ++----+-------+--------------------+ +| 1 | local | NULL | +| 2 | up | 464677399908313272 | ++----+-------+--------------------+ ++----+--------+-----------------+ +| id | text | _tidb_origin_ts | ++----+--------+-----------------+ +| 1 | local | NULL | +| 2 | local2 | NULL | ++----+--------+-----------------+ +``` + +> **注意:** +> +> 不建议业务显式修改 `_tidb_origin_ts`,否则可能导致 Active-Active 同步结果不一致。 + +## Soft Delete(必需) + +Active-Active 表必须同时启用 Soft Delete。Soft Delete 表的语义(DML 改写、`RECOVER VALUES` 数据恢复、后台清理任务等)请参阅 [Soft Delete 表](/soft-delete-table.md)。 + +> **注意:** +> +> 不要在 `tidb_translate_softdelete_sql=OFF` 的情况下对启用 `SOFTDELETE` 的表执行 `DELETE` 操作,否则可能会造成 Active-Active 同步的不一致。更多信息请参阅 [Soft Delete 表](/soft-delete-table.md#软删除语义与数据恢复)。 + +## 监控与排查 + +- 只读 SESSION 变量 [`tidb_cdc_active_active_sync_stats`](/system-variables.md#tidb_cdc_active_active_sync_stats) 仅供 TiCDC 读取,用于获取 Active-Active 同步的冲突跳过统计信息。 +- Soft Delete 表的统计信息(`INFORMATION_SCHEMA.TIDB_SOFTDELETE_TABLE_STATS`)详见 [Soft Delete 表](/soft-delete-table.md#监控与排查)。 + +## 使用限制 + +- Active-Active 表必须同时启用 `SOFTDELETE`。 +- Active-Active 表必须显式指定主键。 +- Active-Active 表(Soft Delete 表)当前不支持 `UNIQUE` 索引(包括 `ADD UNIQUE INDEX` 与 `CREATE UNIQUE INDEX`)。 +- Active-Active 表不支持外键(`FOREIGN KEY`)。 +- Active-Active 表不支持临时表(`TEMPORARY TABLE` / `GLOBAL TEMPORARY TABLE`)。 +- 目前不支持通过 `ALTER TABLE` 修改表的 `ACTIVE_ACTIVE` 启用状态。 +- 不支持通过 DDL 删除、重命名或修改 `_tidb_origin_ts`、`_tidb_softdelete_time` 等内部隐藏列。 +- Active-Active 表同时适用 Soft Delete 表的其他限制,详见 [Soft Delete 表](/soft-delete-table.md#使用限制)。 diff --git a/keywords.md b/keywords.md index 0d6c19b97fbb..3085bd01f5e7 100644 --- a/keywords.md +++ b/keywords.md @@ -61,6 +61,7 @@ TiDB 从 v7.5.3 和 v7.6.0 开始提供 [`INFORMATION_SCHEMA.KEYWORDS`](/informa - ACCOUNT - ACTION +- ACTIVE_ACTIVE - ADD (R) - ADMIN - ADVISE @@ -573,6 +574,7 @@ TiDB 从 v7.5.3 和 v7.6.0 开始提供 [`INFORMATION_SCHEMA.KEYWORDS`](/informa - RESTORES - RESTRICT (R) - RESUME +- RETENTION - REUSE - REVERSE - REVOKE (R) @@ -626,6 +628,9 @@ TiDB 从 v7.5.3 和 v7.6.0 开始提供 [`INFORMATION_SCHEMA.KEYWORDS`](/informa - SLOW - SMALLINT (R) - SNAPSHOT +- SOFTDELETE +- SOFTDELETE_JOB_ENABLE +- SOFTDELETE_JOB_INTERVAL - SOME - SOURCE - SPATIAL (R) diff --git a/pd-configuration-file.md b/pd-configuration-file.md index 27baea8258f2..dc5fe48dd97b 100644 --- a/pd-configuration-file.md +++ b/pd-configuration-file.md @@ -120,6 +120,20 @@ PD 配置文件比命令行参数支持更多的选项。你可以在 [conf/conf + 默认值:50ms + 最小值:1ms +### `tso-max-index` + ++ 用于在多 PD 集群场景下生成全局唯一的 TSO。`tso-max-index` 与 [`tso-unique-index`](#tso-unique-index) 共同决定 TSO 逻辑部分的分配方式,从而避免不同集群产生的 TSO 冲突。 ++ 默认值:0 ++ 当 `tso-max-index` 设置为非 0 值时,需要为每个 PD 集群设置不同的 `tso-unique-index`,并确保 `tso-unique-index` 的取值不超过 `tso-max-index`。 ++ 该配置常用于 Active-Active 场景下跨集群比较时间戳,更多信息请参考 [Active-Active 表](/active-active-table.md)。 + +### `tso-unique-index` + ++ 用于在多 PD 集群场景下标识当前 PD 集群的唯一索引。不同集群必须配置不同值,以避免生成的 TSO 冲突。 ++ 默认值:0 ++ 通常需要与 [`tso-max-index`](#tso-max-index) 配合使用。例如,若有 N 个集群,建议将 `tso-max-index` 设置为 N,并将各集群的 `tso-unique-index` 设置为 1..N(每个集群不同)。 ++ 该配置常用于 Active-Active 场景下跨集群比较时间戳,更多信息请参考 [Active-Active 表](/active-active-table.md)。 + ## pd-server pd-server 相关配置项。 diff --git a/soft-delete-table.md b/soft-delete-table.md new file mode 100644 index 000000000000..e9faca62f7de --- /dev/null +++ b/soft-delete-table.md @@ -0,0 +1,161 @@ +--- +title: Soft Delete 表 +summary: 介绍 Soft Delete 表(SOFTDELETE)的创建方式、语义转换、数据恢复、后台清理任务与限制。 +--- + +# Soft Delete 表 + +Soft Delete(软删除)表通过隐藏列 `_tidb_softdelete_time` 记录删除标记,并通过语义转换将 `DELETE` 重写为“标记删除”,从而实现更长时间的数据保留与可恢复能力。 + +> **说明:** +> +> Active-Active 场景中 Soft Delete 表是冲突解决与删除同步的重要基础能力。Active-Active 表的创建方式与隐藏列含义请参阅 [Active-Active 表](/active-active-table.md)。 + +## 创建 Soft Delete 表 + +Soft Delete 表通过表选项 `SOFTDELETE=RETENTION ...` 启用,也可以通过数据库选项统一启用以便继承。 + +### 通过数据库选项统一启用(推荐) + +```sql +CREATE DATABASE sd_example SOFTDELETE=RETENTION 7 DAY; + +USE sd_example; +CREATE TABLE message ( + id INT PRIMARY KEY, + text VARCHAR(10) +); +``` + +### 在建表时单独启用 + +```sql +CREATE TABLE message ( + id INT PRIMARY KEY, + text VARCHAR(10) +) SOFTDELETE=RETENTION 7 DAY; +``` + +### 调整 Soft Delete 相关选项 + +你可以通过 `ALTER TABLE` 调整 `SOFTDELETE` 的保留期,或配置软删除后台清理任务的开关与执行间隔: + +```sql +-- 调整保留期 +ALTER TABLE message SOFTDELETE=RETENTION 14 DAY; + +-- 配置清理任务开关与执行间隔(例如 '24h'、'30m') +ALTER TABLE message SOFTDELETE_JOB_ENABLE='ON' SOFTDELETE_JOB_INTERVAL='24h'; +``` + +## 软删除语义与数据恢复 + +启用软删除后,TiDB 会为表增加隐藏列 `_tidb_softdelete_time`,并通过系统变量 [`tidb_translate_softdelete_sql`](/system-variables.md#tidb_translate_softdelete_sql) 控制软删除语义: + +- 当 `tidb_translate_softdelete_sql=ON`(默认)时: + - `DELETE` 会被重写为更新 `_tidb_softdelete_time`(标记为软删除)。 + - `SELECT` 会自动过滤软删除数据。 + - 查询中不能显式引用 `_tidb_softdelete_time`。 +- 当 `tidb_translate_softdelete_sql=OFF` 时: + - 软删除数据不会被自动过滤,你可以查询或写入 `_tidb_softdelete_time`。 + +> **注意:** +> +> 不要在 `tidb_translate_softdelete_sql=OFF` 的情况下对启用 `SOFTDELETE` 的表执行 `DELETE` 操作。 +> +> - 对 Soft Delete 表来说,这可能会绕过软删除语义,导致数据被物理删除。 +> - 在 Active-Active 场景中,这还可能造成双向同步的不一致。更多信息请参阅 [Active-Active 表](/active-active-table.md)。 + +### 通过 EXPLAIN 查看 DML 改写 + +当 `tidb_translate_softdelete_sql=ON` 时,你可以通过 `EXPLAIN` 观察到 TiDB 对软删除表 DML 的改写: + +插入(`INSERT`): + +```sql +EXPLAIN INSERT INTO message (id, text) VALUES (1, 'hello'); +``` + +```text ++----------+---------+------+---------------+----------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++----------+---------+------+---------------+----------------------------------------------------------------------------------+ +| Insert_1 | N/A | root | | ReplaceConflictIfExpr: not(isnull(sd_example.message._tidb_softdelete_time)) | ++----------+---------+------+---------------+----------------------------------------------------------------------------------+ +``` + +其中 `ReplaceConflictIfExpr` 表示 `INSERT` 在软删除表上会额外处理“主键冲突但该行已软删除”的情况,从而符合软删除语义。 + +更新(`UPDATE`): + +```sql +EXPLAIN UPDATE message SET text='world' WHERE id=1; +``` + +```text ++---------------------+---------+------+---------------+------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++---------------------+---------+------+---------------+------------------------------------------------------+ +| Update_4 | N/A | root | | N/A | +| └─Selection_7 | 0.00 | root | | isnull(sd_example.message._tidb_softdelete_time) | +| └─Point_Get_6 | 1.00 | root | table:message | handle:1 | ++---------------------+---------+------+---------------+------------------------------------------------------+ +``` + +其中 `Selection` 节点会追加 `isnull(_tidb_softdelete_time)` 过滤条件,确保 `UPDATE` 只作用于未软删除的数据。 + +删除(`DELETE`): + +```sql +EXPLAIN DELETE FROM message WHERE id=1; +``` + +```text ++---------------------+---------+------+---------------+------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++---------------------+---------+------+---------------+------------------------------------------------------+ +| Update_4 | N/A | root | | N/A | +| └─Selection_7 | 0.00 | root | | isnull(sd_example.message._tidb_softdelete_time) | +| └─Point_Get_6 | 1.00 | root | table:message | handle:1 | ++---------------------+---------+------+---------------+------------------------------------------------------+ +``` + +`DELETE` 的执行计划会显示为 `Update`,表示 `DELETE` 会被改写为更新 `_tidb_softdelete_time`(软删除标记),而非物理删除。 + +### 软删除与恢复示例 + +下面示例展示 `DELETE` 执行软删除后的效果,以及如何通过 `RECOVER VALUES` 恢复数据: + +```sql +DROP TABLE IF EXISTS message_recover; +CREATE TABLE message_recover ( + id INT PRIMARY KEY, + text VARCHAR(10) +) SOFTDELETE=RETENTION 7 DAY; + +INSERT INTO message_recover VALUES (1,'hello'); +DELETE FROM message_recover WHERE id=1; + +-- 关闭语义转换,仅用于查看内部隐藏列(不要在 OFF 时执行 DELETE) +SET @@tidb_translate_softdelete_sql=OFF; +SELECT id, text, _tidb_softdelete_time FROM message_recover; + +SET @@tidb_translate_softdelete_sql=ON; +RECOVER VALUES FROM message_recover WHERE id = 1; +SELECT * FROM message_recover; +``` + +软删除数据在保留期到期后,会由后台清理任务执行物理删除(Hard Delete)。你可以通过全局变量 [`tidb_softdelete_job_enable`](/system-variables.md#tidb_softdelete_job_enable) 控制是否调度该清理任务。 + +## 监控与排查 + +- `INFORMATION_SCHEMA.TIDB_SOFTDELETE_TABLE_STATS` 用于查看 Soft Delete 表的行数估算与软删除行数估算(依赖统计信息)。 + +## 使用限制 + +- Soft Delete 表当前不支持 `UNIQUE` 索引(包括 `ADD UNIQUE INDEX` 与 `CREATE UNIQUE INDEX`)。 +- Soft Delete 表不支持外键(`FOREIGN KEY`)。 +- Soft Delete 表不支持多表 `DELETE ... JOIN ...` 等涉及多表的 `DELETE` 语句。 +- Soft Delete 表不支持临时表(`TEMPORARY TABLE` / `GLOBAL TEMPORARY TABLE`)。 +- 不支持通过 DDL 删除、重命名或修改 `_tidb_softdelete_time` 等内部隐藏列。 + diff --git a/sql-statements/sql-statement-alter-database.md b/sql-statements/sql-statement-alter-database.md index 65349150d358..0c63c67ac00f 100644 --- a/sql-statements/sql-statement-alter-database.md +++ b/sql-statements/sql-statement-alter-database.md @@ -15,6 +15,23 @@ AlterDatabaseStmt ::= DatabaseOption ::= DefaultKwdOpt ( CharsetKw '='? CharsetName | 'COLLATE' '='? CollationName | 'ENCRYPTION' '='? EncryptionOpt ) +| ActiveActiveOption +| SoftDeleteOption +| SoftDeleteJobEnableOption +| SoftDeleteJobIntervalOption + +ActiveActiveOption ::= + "ACTIVE_ACTIVE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteOption ::= + "SOFTDELETE" EqOpt "RETENTION" NUM TimeUnit +| "SOFTDELETE" EqOpt 'OFF' + +SoftDeleteJobEnableOption ::= + "SOFTDELETE_JOB_ENABLE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteJobIntervalOption ::= + "SOFTDELETE_JOB_INTERVAL" EqOpt stringLit ``` ## 示例 diff --git a/sql-statements/sql-statement-alter-table.md b/sql-statements/sql-statement-alter-table.md index 91701dbda670..5c2b618a472e 100644 --- a/sql-statements/sql-statement-alter-table.md +++ b/sql-statements/sql-statement-alter-table.md @@ -53,6 +53,10 @@ AlterTableSpec ::= | 'REMOVE' 'TTL' | TTLEnable EqOpt ( 'ON' | 'OFF' ) | TTLJobInterval EqOpt stringLit + | 'SOFTDELETE' EqOpt 'RETENTION' NUM TimeUnit + | 'SOFTDELETE' EqOpt 'OFF' + | 'SOFTDELETE_JOB_ENABLE' EqOpt ( 'ON' | 'OFF' ) + | 'SOFTDELETE_JOB_INTERVAL' EqOpt stringLit ) | PlacementPolicyOption diff --git a/sql-statements/sql-statement-create-database.md b/sql-statements/sql-statement-create-database.md index 10655d6fc7f9..83c0de2eb657 100644 --- a/sql-statements/sql-statement-create-database.md +++ b/sql-statements/sql-statement-create-database.md @@ -28,6 +28,23 @@ DatabaseOptionList ::= DatabaseOption ::= DefaultKwdOpt ( CharsetKw '='? CharsetName | 'COLLATE' '='? CollationName | 'ENCRYPTION' '='? EncryptionOpt ) | DefaultKwdOpt PlacementPolicyOption +| ActiveActiveOption +| SoftDeleteOption +| SoftDeleteJobEnableOption +| SoftDeleteJobIntervalOption + +ActiveActiveOption ::= + "ACTIVE_ACTIVE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteOption ::= + "SOFTDELETE" EqOpt "RETENTION" NUM TimeUnit +| "SOFTDELETE" EqOpt 'OFF' + +SoftDeleteJobEnableOption ::= + "SOFTDELETE_JOB_ENABLE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteJobIntervalOption ::= + "SOFTDELETE_JOB_INTERVAL" EqOpt stringLit PlacementPolicyOption ::= "PLACEMENT" "POLICY" EqOpt PolicyName @@ -47,6 +64,10 @@ CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name create_specification: [DEFAULT] CHARACTER SET [=] charset_name | [DEFAULT] COLLATE [=] collation_name + | ACTIVE_ACTIVE [=] ('ON' | 'OFF') + | SOFTDELETE [=] (RETENTION num time_unit | 'OFF') + | SOFTDELETE_JOB_ENABLE [=] ('ON' | 'OFF') + | SOFTDELETE_JOB_INTERVAL [=] stringLit ``` 当创建已存在的数据库且不指定使用 `IF NOT EXISTS` 时会报错。 diff --git a/sql-statements/sql-statement-create-table.md b/sql-statements/sql-statement-create-table.md index 7b4fdd224fc3..c056d55c9ac4 100644 --- a/sql-statements/sql-statement-create-table.md +++ b/sql-statements/sql-statement-create-table.md @@ -116,7 +116,11 @@ TableOption ::= | 'SECONDARY_ENGINE' EqOpt ( 'NULL' | StringName ) | 'UNION' EqOpt '(' TableNameListOpt ')' | 'ENCRYPTION' EqOpt EncryptionOpt -| 'TTL' EqOpt TimeColumnName '+' 'INTERVAL' Expression TimeUnit (TTLEnable EqOpt ( 'ON' | 'OFF' ))? (TTLJobInterval EqOpt stringLit)? +| ActiveActiveOption +| SoftDeleteOption +| SoftDeleteJobEnableOption +| SoftDeleteJobIntervalOption +| 'TTL' EqOpt TimeColumnName '+' 'INTERVAL' Expression TimeUnit (TTLEnable EqOpt ( 'ON' | 'OFF' ))? (TTLJobInterval EqOpt stringLit)? | PlacementPolicyOption OnCommitOpt ::= @@ -126,6 +130,19 @@ PlacementPolicyOption ::= "PLACEMENT" "POLICY" EqOpt PolicyName | "PLACEMENT" "POLICY" (EqOpt | "SET") "DEFAULT" +ActiveActiveOption ::= + "ACTIVE_ACTIVE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteOption ::= + "SOFTDELETE" EqOpt "RETENTION" NUM TimeUnit +| "SOFTDELETE" EqOpt 'OFF' + +SoftDeleteJobEnableOption ::= + "SOFTDELETE_JOB_ENABLE" EqOpt ( 'ON' | 'OFF' ) + +SoftDeleteJobIntervalOption ::= + "SOFTDELETE_JOB_INTERVAL" EqOpt stringLit + DefaultValueExpr ::= NowSymOptionFractionParentheses | SignedLiteral diff --git a/system-variables.md b/system-variables.md index b087d1d036c6..f7c75fb7ebf4 100644 --- a/system-variables.md +++ b/system-variables.md @@ -1245,6 +1245,16 @@ mysql> SELECT job_info FROM mysql.analyze_jobs ORDER BY end_time DESC LIMIT 1; - 这个变量用于控制是否开启[自动捕获绑定](/sql-plan-management.md#自动捕获绑定-baseline-capturing)功能。该功能依赖 Statement Summary,因此在使用自动绑定之前需打开 Statement Summary 开关。 - 开启该功能后会定期遍历一次 Statement Summary 中的历史 SQL 语句,并为至少出现两次的 SQL 语句自动创建绑定。 +### `tidb_cdc_active_active_sync_stats` + +- 作用域:SESSION +- 是否持久化到集群:否 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 类型:字符串 +- 只读变量,仅供 TiCDC 在 Active-Active 同步过程中读取统计信息,展示当前会话因为冲突而跳过的行数统计信息。 +- 返回值为 JSON 字符串,包含以下字段: + - `conflict_skip_rows`:因冲突检测(Last Write Wins)被跳过写入的行数。 + ### `tidb_cdc_write_source` 从 v6.5.0 版本开始引入 - 作用域:SESSION @@ -5029,6 +5039,16 @@ Query OK, 0 rows affected, 1 warning (0.00 sec) > > 跳过字符检查可能会使 TiDB 检测不到应用写入的非法 UTF-8 字符,进一步导致执行 `ANALYZE` 时解码错误,以及引入其他未知的编码问题。如果应用不能保证写入字符串的合法性,不建议跳过该检查。 +### `tidb_softdelete_job_enable` + +- 作用域:GLOBAL +- 是否持久化到集群:是 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 类型:布尔型 +- 默认值:`ON` +- 这个变量用于控制是否调度 Soft Delete 后台清理任务。 +- 当设置为 `OFF` 时,带 `SOFTDELETE` 选项的表不会执行过期数据的物理删除(Hard Delete)。更多信息请参考 [Soft Delete 表](/soft-delete-table.md)。 + ### `tidb_slow_log_threshold` - 作用域:GLOBAL @@ -5348,6 +5368,20 @@ Query OK, 0 rows affected, 1 warning (0.00 sec) > > 如果禁用该变量,TiDB 可能无法准确跟踪内存使用情况,并且无法控制对应 SQL 语句的内存使用。 +### `tidb_translate_softdelete_sql` + +- 作用域:SESSION | GLOBAL +- 是否持久化到集群:是 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 类型:布尔型 +- 默认值:`ON` +- 该变量用于控制 TiDB 是否对带 `SOFTDELETE` 选项的表启用语义转换(例如将 `DELETE` 重写为更新内部隐藏列 `_tidb_softdelete_time`),以及在读取时过滤已软删除的数据。 +- 当该变量为 `ON` 时,查询中不能显式引用 `_tidb_softdelete_time`。如需查看或手动处理已软删除的数据,请在会话级别将该变量设置为 `OFF`。更多信息请参考 [Soft Delete 表](/soft-delete-table.md)。 + +> **注意:** +> +> 不要在 `tidb_translate_softdelete_sql=OFF` 的情况下对启用 `SOFTDELETE` 的表执行 `DELETE` 操作,否则可能会造成 Active-Active 同步的不一致。更多信息请参考 [Soft Delete 表](/soft-delete-table.md) 和 [Active-Active 表](/active-active-table.md)。 + ### `tidb_tso_client_batch_max_wait_time` 从 v5.3.0 版本开始引入 - 作用域:GLOBAL diff --git a/ticdc/ticdc-active-active-replication.md b/ticdc/ticdc-active-active-replication.md new file mode 100644 index 000000000000..685e2aafc237 --- /dev/null +++ b/ticdc/ticdc-active-active-replication.md @@ -0,0 +1,177 @@ +--- +title: TiCDC Active-Active 双向同步 +summary: 介绍 Active-Active 场景下 TiCDC Changefeed 与 TiDB/PD 的关键配置、以及 DDL 的操作建议。 +--- + +# TiCDC Active-Active 双向同步 + +TiCDC 支持在双向复制(BDR)的基础上启用 Active-Active(双活)同步模式。在该模式下,TiCDC 会把上游变更转换为带冲突解决逻辑的 SQL 写入下游 TiDB,并基于 Last Write Wins(LWW)策略处理多集群多写导致的写入冲突。 + +> **说明:** +> +> Active-Active 同步属于最终一致性方案,不提供跨集群的全局事务一致性。Active-Active 表在 TiDB 层的创建方式与隐藏列含义请参阅 [Active-Active 表](/active-active-table.md),Soft Delete 机制(`SOFTDELETE`、`RECOVER VALUES`、清理任务等)请参阅 [Soft Delete 表](/soft-delete-table.md)。 + +## 部署 Active-Active 同步 + +本节按推荐的部署顺序介绍 Active-Active 双向同步的关键配置与操作,并提供最小可跑通的示例。 + +### 1. 部署两个 TiDB 集群与 TiCDC + +你需要至少两个 TiDB 集群(记为集群 A、集群 B),并在各集群部署 TiCDC,用于建立双向同步链路(A→B 与 B→A)。TiCDC 部署方式可参考 [部署 TiCDC](/ticdc/deploy-ticdc.md)。 + +### 2. 配置并部署 PD(确保跨集群 TSO 可比较) + +Active-Active 场景要求不同集群的 PD 生成的时间戳在全局范围内可比较且不冲突。为此,需要在每个集群的 PD 配置中设置 `tso-max-index` 与 `tso-unique-index`(详见 [PD 配置文件](/pd-configuration-file.md#tso-max-index) 和 [PD 配置文件](/pd-configuration-file.md#tso-unique-index))。 + +示例:两套集群时,可以将 `tso-max-index` 统一设置为 `2`,并为各集群分配不同的 `tso-unique-index`。 + +```toml +# 集群 A 的 pd.toml(示例) +tso-max-index = 2 +tso-unique-index = 0 + +# 集群 B 的 pd.toml(示例) +tso-max-index = 2 +tso-unique-index = 1 +``` + +> **注意:** +> +> 建议为各集群配置 NTP 等时间同步机制,避免因时钟漂移导致事务提交失败或等待时间过长。 + +### 3. 在两侧创建 Active-Active 表(TiDB DDL) + +Active-Active 同步要求需要双向复制的表必须启用 `ACTIVE_ACTIVE='ON'`,并且必须启用 Soft Delete(`SOFTDELETE=RETENTION ...`,详见 [Soft Delete 表](/soft-delete-table.md))。你需要在两个集群上创建**同名同结构**的库表。 + +示例(在集群 A 与集群 B 上分别执行): + +```sql +CREATE DATABASE aa_example ACTIVE_ACTIVE='ON' SOFTDELETE=RETENTION 7 DAY; +USE aa_example; + +CREATE TABLE message ( + id INT PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(100) +); +``` + +更多 Active-Active 表的建表与隐藏列说明,参阅 [Active-Active 表](/active-active-table.md)。更多 Soft Delete 语义与数据恢复说明,参阅 [Soft Delete 表](/soft-delete-table.md)。 + +### 4. 创建双向 changefeed(A→B 与 B→A) + +Active-Active 模式通过 changefeed 配置项 `enable-active-active` 启用,并且**要求同时启用** `bdr-mode`: + +```toml +# changefeed.toml + +# 启用双向复制(BDR)模式 +bdr-mode = true + +# 在 BDR 基础上启用 Active-Active(LWW)同步模式 +enable-active-active = true +``` + +> **注意:** +> +> 如果 changefeed 需要同步 Active-Active 表,请确保已启用 `enable-active-active=true`。否则 TiCDC 可能无法按 Active-Active 的写入与冲突处理逻辑生成下游写入语句,进而导致同步任务异常。 + +然后分别创建两条 changefeed: + +```shell +# 集群 A -> 集群 B +cdc cli changefeed create \ + --server=http://: \ + --changefeed-id="changefeed-ab" \ + --start-ts= \ + --sink-uri="tidb://root@:" \ + --config=changefeed.toml + +# 集群 B -> 集群 A +cdc cli changefeed create \ + --server=http://: \ + --changefeed-id="changefeed-ba" \ + --start-ts= \ + --sink-uri="tidb://root@:" \ + --config=changefeed.toml +``` + +在创建 changefeed 前,建议确保两侧集群在某个时间点的数据处于一致状态。你可以根据实际需要使用 [Dumpling](/dumpling-overview.md) 与 [TiDB Lightning](/tidb-lightning/tidb-lightning-overview.md) 等工具进行全量数据互导,再开启增量双向同步。 + +关于 `--start-ts` 的选择原则(例如双向复制时上下游如何各自选择起始 TSO),参阅 [TiCDC 双向复制](/ticdc/ticdc-bidirectional-replication.md#部署双向复制)。 + +### 5.(可选)调整 Active-Active 专用的 changefeed 参数 + +当 `enable-active-active=true` 且下游为 TiDB 时,TiCDC 会额外执行两类周期性操作: + +- 维护下游的进度表,用于 Hard Delete 安全检查(默认每 `30m` 写入一次)。 +- 读取下游 TiDB 的 `@@tidb_cdc_active_active_sync_stats` 以获取冲突统计(默认每 `1m` 读取一次)。 + +对应配置项如下: + +```toml +# changefeed.toml + +# 进度表更新间隔(默认 30m) +active-active-progress-interval = "30m" + +# 读取 @@tidb_cdc_active_active_sync_stats 的间隔(默认 1m) +# 设置为 "0s" 可关闭冲突统计采集 +active-active-sync-stats-interval = "1m" +``` + +`tidb_cdc_active_active_sync_stats` 系统变量仅用于 TiCDC 读取 Active-Active 同步的统计信息。详见 [`tidb_cdc_active_active_sync_stats`](/system-variables.md#tidb_cdc_active_active_sync_stats)。 + +> **注意:** +> +> - `enable-active-active` 仅支持 TiDB sink(`tidb://...`)以及 storage sink。 +> - `enable-active-active` 与 redo log/consistency 特性不兼容。如需启用 Active-Active,请关闭 redo/consistency 相关配置。 + +## TiDB 侧相关行为与配置 + +### `tidb_translate_softdelete_sql` + +Active-Active 表依赖 `_tidb_softdelete_time` 的语义以及 LWW 写入逻辑。TiCDC 在 Active-Active 模式下写入 TiDB 下游时,会在其连接会话中设置 `tidb_translate_softdelete_sql=OFF`,以便按需读写软删除隐藏列并执行冲突解决逻辑。 + +> **注意:** +> +> 不要在业务会话中将 [`tidb_translate_softdelete_sql`](/system-variables.md#tidb_translate_softdelete_sql) 设为 `OFF` 后再对启用 `SOFTDELETE` 的表执行 `DELETE`,否则可能会造成 Active-Active 同步的不一致。更多信息请参阅 [Soft Delete 表](/soft-delete-table.md) 和 [Active-Active 表](/active-active-table.md)。 + +### TiCDC 系统库 `tidb_cdc` + +当 `enable-active-active=true` 且下游为 TiDB 时,TiCDC 会在下游创建系统库 `tidb_cdc`,并维护一张进度表 `ticdc_progress_table`,用于 Hard Delete 安全检查。 + +建议确保: + +- 下游 TiDB 的 sink 用户具备创建数据库/表及写入 `tidb_cdc` 的权限。 +- 不要将 `tidb_cdc.*` 纳入业务同步范围(TiCDC 会默认跳过该系统库)。 + +## DDL 操作建议 + +在 Active-Active 双向同步中,DDL 的处理方式与 TiCDC 双向复制(BDR)一致:需要通过 BDR role 来降低 DDL 冲突与循环复制风险。 + +### 可复制 DDL + +对“可复制 DDL”,建议: + +1. 选择一个集群作为 DDL 的唯一入口,设置为 `PRIMARY`:`ADMIN SET BDR ROLE PRIMARY;` +2. 其他集群设置为 `SECONDARY`:`ADMIN SET BDR ROLE SECONDARY;` +3. 只在 `PRIMARY` 集群执行可复制 DDL,TiCDC 会将其同步到 `SECONDARY` 集群。 + +可复制/不可复制 DDL 的划分以及具体操作步骤,详见 [TiCDC 双向复制](/ticdc/ticdc-bidirectional-replication.md#ddl-类别)。 + +### 不可复制 DDL + +对“不可复制 DDL”,建议按以下流程降低不一致风险: + +1. 对所有集群执行 `ADMIN UNSET BDR ROLE;` +2. 暂停相关表的业务写入,并等待增量同步追平 +3. 在每个集群上分别执行 DDL +4. 恢复写入并重新设置 BDR role + +详细步骤与注意事项,详见 [TiCDC 双向复制](/ticdc/ticdc-bidirectional-replication.md#不可复制的-ddl-的同步场景)。 + +### Active-Active 特有注意事项 + +- 需要双向同步的表应以 Active-Active 表形式创建,并保持各集群表结构与表选项一致。建表与选项说明详见 [Active-Active 表](/active-active-table.md)。 +- 不支持通过 DDL 删除、重命名或修改 Active-Active 表的内部隐藏列(例如 `_tidb_origin_ts`、`_tidb_softdelete_time`),也不支持在建表后切换表的 `ACTIVE_ACTIVE` 启用状态。详见 [Active-Active 表](/active-active-table.md#使用限制)。 From e9c27661cfc94222e2144b5127b7f8bbb100dc9b Mon Sep 17 00:00:00 2001 From: Chao Wang Date: Mon, 16 Mar 2026 12:06:06 +0800 Subject: [PATCH 2/2] add some comment