From 18e617aaa34681a2dad6108d4f125b64326fa9b4 Mon Sep 17 00:00:00 2001 From: ChangRui-Ryan Date: Mon, 10 Nov 2025 12:06:31 +0800 Subject: [PATCH 01/11] Do not merge until v8.5.4: MySQL compatibility about decimal insert through jdbc (#21046) --- develop/dev-guide-sample-application-java-jdbc.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/develop/dev-guide-sample-application-java-jdbc.md b/develop/dev-guide-sample-application-java-jdbc.md index 31e6d04777e7..d7116b92f072 100644 --- a/develop/dev-guide-sample-application-java-jdbc.md +++ b/develop/dev-guide-sample-application-java-jdbc.md @@ -280,6 +280,17 @@ Java 驱动程序提供对数据库的底层访问,但要求开发者: - 减少管理连接和事务的[模板代码](https://en.wikipedia.org/wiki/Boilerplate_code) - 使用数据对象代替大量 SQL 语句来操作数据 +### MySQL 兼容性 + +在 MySQL 中,当写入 `DECIMAL` 类型的数据时,如果小数位数超过字段定义的小数位数,无论超出多少,都会自动截断多余的位数并成功插入。 + +在 TiDB v8.5.3 及之前版本中: + +- 如果小数位数超过字段定义的小数位数但未超过 72 位,同样会自动截断多余的位数并成功插入。 +- 如果小数位数超过 72 位,写入会失败并报错。 + +从 TiDB v8.5.4 开始,TiDB 的行为和 MySQL 保持一致:无论小数位数超过多少,都会自动截断多余的位数并成功插入。 + ## 下一步 - 关于 MySQL Connector/J 的更多使用方法,可以参考 [MySQL Connector/J 官方文档](https://dev.mysql.com/doc/connector-j/en/)。 From 66a6ae1e5fbba433c81081f3180f86621679fcf5 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 13:37:10 +0800 Subject: [PATCH 02/11] Add docs for Analyze Embedded in DDL and new system variable (#21031) (#21060) --- TOC.md | 1 + ddl_embedded_analyze.md | 177 ++++++++++++++++++++++++++++++++++++++++ system-variables.md | 8 ++ 3 files changed, 186 insertions(+) create mode 100644 ddl_embedded_analyze.md diff --git a/TOC.md b/TOC.md index 6b672d43594e..fe0bdbbfed84 100644 --- a/TOC.md +++ b/TOC.md @@ -1079,6 +1079,7 @@ - [通过拓扑 label 进行副本调度](/schedule-replicas-by-topology-labels.md) - [外部存储服务的 URI 格式](/external-storage-uri.md) - [线上负载与 `ADD INDEX` 相互影响测试](/benchmark/online-workloads-and-add-index-operations.md) + - [内嵌于 DDL 的 Analyze](/ddl_embedded_analyze.md) - 常见问题解答 (FAQ) - [FAQ 汇总](/faq/faq-overview.md) - [产品 FAQ](/faq/tidb-faq.md) diff --git a/ddl_embedded_analyze.md b/ddl_embedded_analyze.md new file mode 100644 index 000000000000..76ad8d2535ef --- /dev/null +++ b/ddl_embedded_analyze.md @@ -0,0 +1,177 @@ +--- +title: 内嵌于 DDL 的 Analyze +summary: 本文介绍内嵌于新建或重组索引的 DDL 中的 Analyze 特性,用于确保新索引的统计信息及时更新。 +--- + +# 内嵌于 DDL 的 Analyze 从 v8.5.4 和 v9.0.0 开始引入 + +本文介绍内嵌于以下两类 DDL 的 Analyze 特性: + +- 新建索引的 DDL:[`ADD INDEX`](/sql-statements/sql-statement-add-index.md) +- 重组已有索引的 DDL:[`MODIFY COLUMN`](/sql-statements/sql-statement-modify-column.md) 和 [`CHANGE COLUMN`](/sql-statements/sql-statement-change-column.md) + +开启该特性后,TiDB 会在新索引对用户可见前自动执行一次 Analyze(统计信息收集),以避免新建或重组索引后因统计信息暂不可用而导致优化器估算不准,从而引起执行计划变更的问题。 + +## 使用场景 + +在一些交替执行索引新增或修改的 DDL 操作场景中,已有的稳定查询可能因为新索引缺乏统计信息而出现代价估算偏差,导致优化器生成次优计划。详情可参考 [Issue #57948](https://github.com/pingcap/tidb/issues/57948)。 + +例如: + +```sql +CREATE TABLE t (a INT, b INT); +INSERT INTO t VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO t SELECT * FROM t; -- * N times + +ALTER TABLE t ADD INDEX idx_a (a); + +EXPLAIN SELECT * FROM t WHERE a > 4; +``` + +``` ++-------------------------+-----------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+-----------+-----------+---------------+--------------------------------+ +| TableReader_8 | 131072.00 | root | | data:Selection_7 | +| └─Selection_7 | 131072.00 | cop[tikv] | | gt(test.t.a, 4) | +| └─TableFullScan_6 | 393216.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------+-----------+-----------+---------------+--------------------------------+ +3 rows in set (0.002 sec) +``` + +从以上执行计划可以看到,由于新建索引尚未生成统计信息,TiDB 在路径估算时只能依赖启发式规则。除非索引访问路径无需回表且代价显著更低,否则优化器倾向于选择估算更稳定的现有路径,因此上述示例中使用了全表扫描。然而,从数据分布角度来看,`t.a > 4` 实际返回 0 行,如果能使用新建索引 `idx_a`,查询可以快速定位到相关行,从而避免全表扫描。在该示例中,由于 DDL 创建索引后 TiDB 未能及时收集索引统计信息,生成的执行计划不是最优的,但优化器会继续沿用原有计划,因此查询性能不会出现突变或退化。然而,根据 [Issue #57948](https://github.com/pingcap/tidb/issues/57948),在某些情况下,启发式规则可能会导致新旧索引进行不合理的比较,从而裁剪原查询计划依赖的索引,最终 fallback 到全表扫描。 + +从 v8.5.0 起,TiDB 对索引的启发式比较和统计信息缺失时的行为进行了优化。但在部分复杂场景中,在 DDL 执行过程中内嵌 Analyze 仍是防止执行计划变更的最佳方案。你可以通过系统变量 [`tidb_stats_update_during_ddl`](/system-variables.md#tidb_stats_update_during_ddl-从-v854-和-v900-版本开始引入) 控制在索引创建或重组阶段是否执行内嵌 Analyze。该变量默认值为 `OFF`。 + +## 新建索引 `ADD INDEX` 的 DDL + +当 `tidb_stats_update_during_ddl` 设置为 `ON` 时,执行 [`ADD INDEX`](/sql-statements/sql-statement-add-index.md) 操作将在 Reorg 阶段结束后自动执行内嵌的 Analyze 命令。此 Analyze 命令会在新索引对用户可见前,分析相关新建索引的统计信息,然后再继续执行 `ADD INDEX` 的剩余阶段。 + +考虑到 Analyze 可能会有一定的耗时,TiDB 会以首次 Reorg 的执行时间为参考设置超时阈值。若 Analyze 超时,`ADD INDEX` 将不再同步等待 Analyze 完成,而是继续执行后续流程,使索引提前对用户可见。这意味着,该新索引的统计信息会在 Analyze 异步完成后更新。 + +示例: + +```sql +CREATE TABLE t (a INT, b INT, c INT); +Query OK, 0 rows affected (0.011 sec) + +INSERT INTO t VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); +Query OK, 3 rows affected (0.003 sec) +Records: 3 Duplicates: 0 Warnings: 0 + +SET @@tidb_stats_update_during_ddl = 1; +Query OK, 0 rows affected (0.001 sec) + +ALTER TABLE t ADD INDEX idx (a, b); +Query OK, 0 rows affected (0.049 sec) +``` + +```sql +EXPLAIN SELECT a FROM t WHERE a > 1; +``` + +``` ++------------------------+---------+-----------+--------------------------+----------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+--------------------------+----------------------------------+ +| IndexReader_7 | 4.00 | root | | index:IndexRangeScan_6 | +| └─IndexRangeScan_6 | 4.00 | cop[tikv] | table:t, index:idx(a, b) | range:(1,+inf], keep order:false | ++------------------------+---------+-----------+--------------------------+----------------------------------+ +2 rows in set (0.002 sec) +``` + +```sql +SHOW STATS_HISTOGRAMS WHERE table_name = "t"; +``` + +``` ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +| Db_name | Table_name | Partition_name | Column_name | Is_index | Update_time | Distinct_count | Null_count | Avg_col_size | Correlation | Load_status | Total_mem_usage | Hist_mem_usage | Topn_mem_usage | Cms_mem_usage | ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +| test | t | | a | 0 | 2025-10-30 20:17:57 | 3 | 0 | 0.5 | 1 | allLoaded | 155 | 0 | 155 | 0 | +| test | t | | idx | 1 | 2025-10-30 20:17:57 | 3 | 0 | 0 | 0 | allLoaded | 182 | 0 | 182 | 0 | ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +2 rows in set (0.013 sec) +``` + +```sql +ADMIN SHOW DDL JOBS 1; +``` + +``` ++--------+---------+--------------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+----------------------------------------+ +| JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | CREATE_TIME | START_TIME | END_TIME | STATE | COMMENTS | ++--------+---------+--------------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+----------------------------------------+ +| 151 | test | t | add index | write reorganization | 2 | 148 | 6291456 | 2025-10-29 00:14:47.181000 | 2025-10-29 00:14:47.183000 | NULL | running | analyzing, txn-merge, max_node_count=3 | ++--------+---------+--------------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+----------------------------------------+ +1 rows in set (0.001 sec) +``` + +从 `ADD INDEX` 示例来看,当 `tidb_stats_update_during_ddl` 设置为 `ON` 时,在 `ADD INDEX` DDL 执行结束后,可以看到其之后运行的 `EXPLAIN` 查询中,相关索引 `idx` 的统计信息已经被自动收集并加载到内存中(可通过 `SHOW STATS_HISTOGRAMS` 语句的输出结果得到验证)。因此,优化器可以立即在范围扫描(Range Scan)中使用这些统计信息。如果索引的创建或重组以及 Analyze 过程耗时较长,可以通过 `ADMIN SHOW DDL JOBS` 查看 DDL Job 的状态。当输出结果中的 `COMMENTS` 列包含 `analyzing` 时,表示该 DDL Job 正在执行统计信息收集。 + +## 重组已有索引的 DDL + +当 `tidb_stats_update_during_ddl` 设置为 `ON` 时,执行 [`MODIFY COLUMN`](/sql-statements/sql-statement-modify-column.md) 或 [`CHANGE COLUMN`](/sql-statements/sql-statement-change-column.md) 操作重组索引时,TiDB 也会在 Reorg 阶段结束后执行内嵌的 Analyze 命令。其机制与 `ADD INDEX` 相同: + +- 在索引可见前开始进行统计信息收集。 +- 若 Analyze 超时,[`MODIFY COLUMN`](/sql-statements/sql-statement-modify-column.md) 和 [`CHANGE COLUMN`](/sql-statements/sql-statement-change-column.md) 将不会同步等待 Analyze 完成,而是继续执行后续流程,使索引提前对用户可见。这意味着,该新索引的统计信息会在 Analyze 异步完成后更新。 + +示例: + +```sql +CREATE TABLE s (a VARCHAR(10), INDEX idx (a)); +Query OK, 0 rows affected (0.012 sec) + +INSERT INTO s VALUES (1), (2), (3); +Query OK, 3 rows affected (0.003 sec) +Records: 3 Duplicates: 0 Warnings: 0 + +SET @@tidb_stats_update_during_ddl = 1; +Query OK, 0 rows affected (0.001 sec) + +ALTER TABLE s MODIFY COLUMN a INT; +Query OK, 0 rows affected (0.056 sec) + +EXPLAIN SELECT * FROM s WHERE a > 1; +``` + +``` ++------------------------+---------+-----------+-----------------------+----------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+-----------------------+----------------------------------+ +| IndexReader_7 | 2.00 | root | | index:IndexRangeScan_6 | +| └─IndexRangeScan_6 | 2.00 | cop[tikv] | table:s, index:idx(a) | range:(1,+inf], keep order:false | ++------------------------+---------+-----------+-----------------------+----------------------------------+ +2 rows in set (0.005 sec) +``` + +```sql +SHOW STATS_HISTOGRAMS WHERE table_name = "s"; +``` + +``` ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +| Db_name | Table_name | Partition_name | Column_name | Is_index | Update_time | Distinct_count | Null_count | Avg_col_size | Correlation | Load_status | Total_mem_usage | Hist_mem_usage | Topn_mem_usage | Cms_mem_usage | ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +| test | s | | a | 0 | 2025-10-30 20:10:18 | 3 | 0 | 2 | 1 | allLoaded | 158 | 0 | 158 | 0 | +| test | s | | a | 0 | 2025-10-30 20:10:18 | 3 | 0 | 1 | 1 | allLoaded | 155 | 0 | 155 | 0 | +| test | s | | idx | 1 | 2025-10-30 20:10:18 | 3 | 0 | 0 | 0 | allLoaded | 158 | 0 | 158 | 0 | +| test | s | | idx | 1 | 2025-10-30 20:10:18 | 3 | 0 | 0 | 0 | allLoaded | 155 | 0 | 155 | 0 | ++---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+-------------+-----------------+----------------+----------------+---------------+ +4 rows in set (0.008 sec) +``` + +```sql +ADMIN SHOW DDL JOBS 1; +``` + +``` ++--------+---------+------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+-----------------------------+ +| JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | CREATE_TIME | START_TIME | END_TIME | STATE | COMMENTS | ++--------+---------+------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+-----------------------------+ +| 153 | test | s | modify column | write reorganization | 2 | 148 | 12582912 | 2025-10-29 00:26:49.240000 | 2025-10-29 00:26:49.244000 | NULL | running | analyzing | ++--------+---------+------------------+---------------+----------------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+---------+-----------------------------+ +1 rows in set (0.001 sec) +``` + +从 `MODIFY COLUMN` 示例来看,当 `tidb_stats_update_during_ddl` 设置为 `ON` 时,在 `MODIFY COLUMN` DDL 执行结束后,可以看到其之后运行的 `EXPLAIN` 查询中,相关索引 `idx` 的统计信息已经被自动收集并加载到内存中(可通过 `SHOW STATS_HISTOGRAMS` 语句的输出结果得到验证),因此优化器能够立即在范围扫描(Range Scan)中使用这些统计信息。如果索引的创建或重组以及 Analyze 过程耗时较长,可以通过 `ADMIN SHOW DDL JOBS` 查看 DDL Job 的状态。当输出结果中的 `COMMENTS` 列包含 `analyzing` 时,表示该 DDL Job 正在执行统计信息收集。 diff --git a/system-variables.md b/system-variables.md index c5c51bd76155..804abb571bb1 100644 --- a/system-variables.md +++ b/system-variables.md @@ -1446,6 +1446,14 @@ mysql> SELECT job_info FROM mysql.analyze_jobs ORDER BY end_time DESC LIMIT 1; > > * 在升级到 v6.5.0 及以上版本时,请确保 TiDB 的 [`temp-dir`](/tidb-configuration-file.md#temp-dir-从-v630-版本开始引入) 路径已正确挂载了 SSD 磁盘,并确保运行 TiDB 的操作系统用户对该目录有读写权限,否则在运行时可能产生不可预知的问题。该参数是 TiDB 的配置参数,设置后需要重启 TiDB 才能生效。因此,在升级前提前进行设置,可以避免再次重启。 +### `tidb_stats_update_during_ddl` 从 v8.5.4 和 v9.0.0 版本开始引入 + +- 作用域:SESSION | GLOBAL +- 是否持久化到集群:是 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 默认值:`OFF` +- 这个变量用于控制是否开启 DDL 内嵌的 Analyze 的行为。开启后,涉及新建索引的 DDL [`ADD INDEX`](/sql-statements/sql-statement-add-index.md),以及重组已有索引的 DDL([`MODIFY COLUMN`](/sql-statements/sql-statement-modify-column.md) 和 [`CHANGE COLUMN`](/sql-statements/sql-statement-change-column.md))将会在索引可见前自动执行统计信息收集。详情请参考[内嵌于 DDL 的 Analyze](/ddl_embedded_analyze.md)。 + ### `tidb_enable_dist_task` 从 v7.1.0 版本开始引入 - 作用域:GLOBAL From ba9f2cac8e3a480565cbad7ed26d71d2f39f134d Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 14:37:19 +0800 Subject: [PATCH 03/11] planner: Add subquery variables (#21032) (#21056) --- system-variables.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/system-variables.md b/system-variables.md index 804abb571bb1..40d63ce320bd 100644 --- a/system-variables.md +++ b/system-variables.md @@ -3703,6 +3703,24 @@ mysql> desc select count(distinct a) from test.t; - 默认值:`OFF` - 该变量控制是否开启[跨数据库绑定执行计划](/sql-plan-management.md#跨数据库绑定执行计划-cross-db-binding)功能。 +### `tidb_opt_enable_no_decorrelate_in_select` 从 v8.5.4 版本开始引入 + +- 作用域:SESSION | GLOBAL +- 是否持久化到集群:是 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:是 +- 类型:布尔型 +- 默认值:`OFF` +- 该变量控制优化器是否对 `SELECT` 列表中包含子查询的所有查询应用 [`NO_DECORRELATE()`](/optimizer-hints.md#no_decorrelate) Hint。 + +### `tidb_opt_enable_semi_join_rewrite` 从 v8.5.4 版本开始引入 + +- 作用域:SESSION | GLOBAL +- 是否持久化到集群:是 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 类型:布尔型 +- 默认值:`OFF` +- 该变量控制优化器是否对包含子查询的所有查询应用 [`SEMI_JOIN_REWRITE()`](/optimizer-hints.md#semi_join_rewrite) Hint。 + ### `tidb_opt_fix_control` 从 v6.5.3 和 v7.1.0 版本开始引入 - 作用域:SESSION | GLOBAL From 3aef0cd3bddda8ac9820a332e8fc5357fadd5b0b Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 16:55:26 +0800 Subject: [PATCH 04/11] v8.5.4: tiflash: add a config named `graceful_shutdown_wait_timeout` (#20894) (#21048) --- tiflash/tiflash-configuration.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tiflash/tiflash-configuration.md b/tiflash/tiflash-configuration.md index 81aecd93422d..1db1ff6cf4ca 100644 --- a/tiflash/tiflash-configuration.md +++ b/tiflash/tiflash-configuration.md @@ -239,6 +239,13 @@ I/O 限流功能相关配置。 - 该配置只针对存算分离模式生效,详细请参考 [TiFlash 存算分离架构与 S3 支持](/tiflash/tiflash-disaggregated-and-s3.md)。 - 可选值:`"tiflash_write"`、`"tiflash_compute"` +##### `graceful_wait_shutdown_timeout` 从 v8.5.4 开始引入 + +- 控制在关闭 TiFlash 服务器时的最长等待时间。在此期间,TiFlash 允许尚未完成的 MPP 任务继续执行,但不再接收新的 MPP 任务。如果所有正在运行的 MPP 任务都在此超时时间之前完成,TiFlash 将立即关闭;否则将在等待时间结束后强制关闭。 +- 默认值:`600` +- 单位:秒 +- 在等待 TiFlash 服务器关闭期间,TiDB 不会再向该 TiFlash 服务器发送新的 MPP 任务。 + #### flash.proxy ##### `addr` From e5746cc06a04d2bd3edeb5501984e5174ffb3ca2 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 17:37:40 +0800 Subject: [PATCH 05/11] v8.5.4: partitioned-table: global index supports non-unique index (#19507) (#20982) --- partitioned-table.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/partitioned-table.md b/partitioned-table.md index 94367c7df1eb..0004c86b1485 100644 --- a/partitioned-table.md +++ b/partitioned-table.md @@ -1684,13 +1684,13 @@ CREATE TABLE t (a varchar(20), b blob, ERROR 8264 (HY000): Global Index is needed for index 'a', since the unique index is not including all partitioning columns, and GLOBAL is not given as IndexOption ``` -#### 全局索引 +### 全局索引 在引入全局索引 (Global Index) 之前,TiDB 会为每个分区创建一个局部索引 (Local Index),即一个分区对应一个局部索引。这种索引方式存在一个[使用限制](#分区键主键和唯一键):主键和唯一键必须包含所有的分区键,以确保数据的全局唯一性。此外,当查询的数据跨越多个分区时,TiDB 需要扫描各个分区的数据才能返回结果。 -为解决这些问题,TiDB 从 v8.3.0 开始引入全局索引。全局索引能覆盖整个表的数据,使得主键和唯一键在不包含分区键的情况下仍能保持全局唯一性。此外,全局索引可以在一次操作中访问多个分区的索引数据,而无需对每个分区的本地索引逐一查找,显著提升了针对非分区键的查询性能。 +为解决这些问题,TiDB 从 v8.3.0 开始引入全局索引。全局索引能覆盖整个表的数据,使得主键和唯一键在不包含分区键的情况下仍能保持全局唯一性。此外,全局索引可以在一次操作中访问多个分区的索引数据,而无需对每个分区的局部索引逐一查找,显著提升了针对非分区键的查询性能。从 v8.5.4 开始,非唯一索引也可以创建为全局索引。 -如果你需要为主键或唯一键创建全局索引,可以通过在索引定义中添加 `GLOBAL` 关键字来实现。 +如果你需要创建全局索引,可以通过在索引定义中添加 `GLOBAL` 关键字来实现。 > **注意:** > @@ -1703,13 +1703,14 @@ CREATE TABLE t1 ( col3 INT NOT NULL, col4 INT NOT NULL, UNIQUE KEY uidx12(col1, col2) GLOBAL, - UNIQUE KEY uidx3(col3) + UNIQUE KEY uidx3(col3), + KEY idx1(col1) GLOBAL ) PARTITION BY HASH(col3) PARTITIONS 4; ``` -在上面示例中,唯一索引 `uidx12` 将成为全局索引,但 `uidx3` 仍是常规的唯一索引。 +在上面示例中,唯一索引 `uidx12` 和非唯一索引 `idx1` 将成为全局索引,但 `uidx3` 仍是常规的唯一索引。 请注意,**聚簇索引**不能成为全局索引,如下例所示: @@ -1741,7 +1742,8 @@ Create Table: CREATE TABLE `t1` ( `col3` int NOT NULL, `col4` int NOT NULL, UNIQUE KEY `uidx12` (`col1`,`col2`) /*T![global_index] GLOBAL */, - UNIQUE KEY `uidx3` (`col3`) + UNIQUE KEY `uidx3` (`col3`), + KEY `idx1` (`col1`) /*T![global_index] GLOBAL */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin PARTITION BY HASH (`col3`) PARTITIONS 4 1 row in set (0.00 sec) @@ -1760,26 +1762,23 @@ SELECT * FROM information_schema.tidb_indexes WHERE table_name='t1'; | test | t1 | 0 | uidx12 | 1 | col1 | NULL | | NULL | 1 | YES | NO | 1 | | test | t1 | 0 | uidx12 | 2 | col2 | NULL | | NULL | 1 | YES | NO | 1 | | test | t1 | 0 | uidx3 | 1 | col3 | NULL | | NULL | 2 | YES | NO | 0 | +| test | t1 | 1 | idx1 | 1 | col1 | NULL | | NULL | 3 | YES | NO | 1 | +--------------+------------+------------+----------+--------------+-------------+----------+---------------+------------+----------+------------+-----------+-----------+ 3 rows in set (0.00 sec) ``` -在对未分区的表进行分区,或对已分区的表进行重新分区时,可以根据需要将索引更新为全局索引或将其还原为本地索引: +在对普通表进行分区或者对分区表进行重新分区时,可以根据需要将索引更新为全局索引或局部索引。 + +例如,下面的 SQL 语句会基于 `col1` 列对表 `t1` 进行重新分区,并将该表中的全局索引 `uidx12` 和 `idx1` 更新为局部索引,将局部索引 `uidx3` 更新为全局索引。`uidx3` 是基于 `col3` 列的唯一索引,为了保证 `col3` 在所有分区中的唯一性,`uidx3` 必须为全局索引;`uidx12` 和 `idx1` 是基于 `col1` 列的索引,因此可以是全局索引或局部索引。 ```sql -ALTER TABLE t1 PARTITION BY HASH (col1) PARTITIONS 3 UPDATE INDEXES (uidx12 LOCAL, uidx3 GLOBAL); +ALTER TABLE t1 PARTITION BY HASH (col1) PARTITIONS 3 UPDATE INDEXES (uidx12 LOCAL, uidx3 GLOBAL, idx1 LOCAL); ``` -##### 全局索引的限制 +#### 全局索引的限制 - 如果索引定义中未显式指定 `GLOBAL` 关键字,TiDB 将默认创建局部索引 (Local Index)。 - `GLOBAL` 和 `LOCAL` 关键字仅适用于分区表,对非分区表没有影响。即在非分区表中,全局索引和局部索引之间没有区别。 -- 当前仅支持为唯一列创建全局索引 (Unique Global Index)。如果需要对非唯一列创建全局索引,可以通过包含主键形成复合索引。例如,如果非唯一列是 `col3` 而主键是 `col1`,可以通过执行以下 SQL 语句为 `col3` 创建全局索引: - - ```sql - ALTER TABLE ... ADD UNIQUE INDEX(col3, col1) GLOBAL; - ``` - - 以下 DDL 操作会触发全局索引的更新:`DROP PARTITION`、`TRUNCATE PARTITION` 和 `REORGANIZE PARTITION`。这些 DDL 需等待全局索引更新完成后才会返回结果,耗时会相应增加。尤其是在数据归档场景下,如 `DROP PARTITION` 和 `TRUNCATE PARTITION`,若没有全局索引,通常可以立即完成;但使用全局索引后,耗时会随着所需更新的索引数量的增加而增加。 - 包含全局索引的表不支持 `EXCHANGE PARTITION`。 - 默认情况下,分区表的主键为聚簇索引,且必须包含分区键。如果要求主键不包含分区建,可以在建表时显式指定主键为非聚簇的全局索引,例如:`PRIMARY KEY(col1, col2) NONCLUSTERED GLOBAL`。 @@ -1913,7 +1912,7 @@ select * from t; 5 rows in set (0.00 sec) ``` -### 动态裁剪模式 +## 动态裁剪模式 TiDB 访问分区表有两种模式,`dynamic` 和 `static`。从 v6.3.0 开始,默认使用 `dynamic` 模式。但是注意,`dynamic` 模式仅在表级别汇总统计信息(即分区表的全局统计信息)收集完成的情况下生效。如果在全局统计信息未收集完成的情况下启用 `dynamic` 动态裁剪模式,TiDB 仍然会维持 `static` 静态裁剪的状态,直到全局统计信息收集完成。关于全局统计信息的更多信息,请参考[动态裁剪模式下的分区表统计信息](/statistics.md#收集动态裁剪模式下的分区表统计信息)。 @@ -2118,7 +2117,7 @@ mysql> explain select /*+ TIDB_INLJ(t1, t2) */ t1.* from t1, t2 where t2.code = 目前,静态裁剪模式不支持执行计划缓存,包括 Prepare 语句和非 Prepare 语句。 -#### 为动态裁剪模式更新所有分区表的统计信息 +### 为动态裁剪模式更新所有分区表的统计信息 1. 找到所有的分区表: From 4024f10e3695a889ca1298a35670cc316e19522b Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 17:37:56 +0800 Subject: [PATCH 06/11] v8.5.4: distribute-table: support to scatter the region distribution of the given table and engine (#19534) (#20985) --- TOC.md | 4 + .../sql-statement-cancel-distribution-job.md | 38 ++++++ .../sql-statement-distribute-table.md | 120 ++++++++++++++++++ .../sql-statement-show-distribution-jobs.md | 43 +++++++ .../sql-statement-show-table-distribution.md | 60 +++++++++ 5 files changed, 265 insertions(+) create mode 100644 sql-statements/sql-statement-cancel-distribution-job.md create mode 100644 sql-statements/sql-statement-distribute-table.md create mode 100644 sql-statements/sql-statement-show-distribution-jobs.md create mode 100644 sql-statements/sql-statement-show-table-distribution.md diff --git a/TOC.md b/TOC.md index fe0bdbbfed84..34418a0cc99c 100644 --- a/TOC.md +++ b/TOC.md @@ -815,6 +815,7 @@ - [`BATCH`](/sql-statements/sql-statement-batch.md) - [`BEGIN`](/sql-statements/sql-statement-begin.md) - [`CALIBRATE RESOURCE`](/sql-statements/sql-statement-calibrate-resource.md) + - [`CANCEL DISTRIBUTION JOB`](/sql-statements/sql-statement-cancel-distribution-job.md) - [`CANCEL IMPORT JOB`](/sql-statements/sql-statement-cancel-import-job.md) - [`COMMIT`](/sql-statements/sql-statement-commit.md) - [`CREATE BINDING`](/sql-statements/sql-statement-create-binding.md) @@ -832,6 +833,7 @@ - [`DELETE`](/sql-statements/sql-statement-delete.md) - [`DESC`](/sql-statements/sql-statement-desc.md) - [`DESCRIBE`](/sql-statements/sql-statement-describe.md) + - [`DISTRIBUTE TABLE`](/sql-statements/sql-statement-distribute-table.md) - [`DO`](/sql-statements/sql-statement-do.md) - [`DROP BINDING`](/sql-statements/sql-statement-drop-binding.md) - [`DROP DATABASE`](/sql-statements/sql-statement-drop-database.md) @@ -896,6 +898,7 @@ - [`SHOW CREATE DATABASE`](/sql-statements/sql-statement-show-create-database.md) - [`SHOW CREATE USER`](/sql-statements/sql-statement-show-create-user.md) - [`SHOW DATABASES`](/sql-statements/sql-statement-show-databases.md) + - [`SHOW DISTRIBUTION JOBS`](/sql-statements/sql-statement-show-distribution-jobs.md) - [`SHOW ENGINES`](/sql-statements/sql-statement-show-engines.md) - [`SHOW ERRORS`](/sql-statements/sql-statement-show-errors.md) - [`SHOW FIELDS FROM`](/sql-statements/sql-statement-show-fields-from.md) @@ -918,6 +921,7 @@ - [`SHOW STATS_META`](/sql-statements/sql-statement-show-stats-meta.md) - [`SHOW STATS_TOPN`](/sql-statements/sql-statement-show-stats-topn.md) - [`SHOW STATUS`](/sql-statements/sql-statement-show-status.md) + - [`SHOW TABLE DISTRIBUTION`](/sql-statements/sql-statement-show-table-distribution.md) - [`SHOW TABLE NEXT_ROW_ID`](/sql-statements/sql-statement-show-table-next-rowid.md) - [`SHOW TABLE REGIONS`](/sql-statements/sql-statement-show-table-regions.md) - [`SHOW TABLE STATUS`](/sql-statements/sql-statement-show-table-status.md) diff --git a/sql-statements/sql-statement-cancel-distribution-job.md b/sql-statements/sql-statement-cancel-distribution-job.md new file mode 100644 index 000000000000..737935df7b18 --- /dev/null +++ b/sql-statements/sql-statement-cancel-distribution-job.md @@ -0,0 +1,38 @@ +--- +title: CANCEL DISTRIBUTION JOB +summary: TiDB 数据库中 CANCEL DISTRIBUTION JOB 的使用情况。 +--- + +# CANCEL DISTRIBUTION JOB 从 v8.5.4 开始引入 + +`CANCEL DISTRIBUTION JOB` 语句用于取消 TiDB 中通过 [`DISTRIBUTE TABLE`](/sql-statements/sql-statement-distribute-table.md) 语句创建的 Region 调度任务。 + +## 语法图 + +```ebnf+diagram +CancelDistributionJobsStmt ::= + 'CANCEL' 'DISTRIBUTION' 'JOB' JobID +``` + +## 示例 + +下面示例取消 ID 为 1 的导入任务: + +```sql +CANCEL DISTRIBUTION JOB 1; +``` + +输出结果如下: + +``` +Query OK, 0 rows affected (0.01 sec) +``` + +## MySQL 兼容性 + +该语句是 TiDB 对 MySQL 语法的扩展。 + +## 另请参阅 + +* [`DISTRIBUTE TABLE`](/sql-statements/sql-statement-distribute-table.md) +* [`SHOW DISTRIBUTION JOBS`](/sql-statements/sql-statement-show-distribution-jobs.md) diff --git a/sql-statements/sql-statement-distribute-table.md b/sql-statements/sql-statement-distribute-table.md new file mode 100644 index 000000000000..acc926e6de8e --- /dev/null +++ b/sql-statements/sql-statement-distribute-table.md @@ -0,0 +1,120 @@ +--- +title: DISTRIBUTE TABLE +summary: 介绍 TiDB 数据库中 DISTRIBUTE TABLE 的使用概况。 +--- + +# DISTRIBUTE TABLE 从 v8.5.4 开始引入 + +> **警告:** +> +> 该功能目前为实验特性,不建议在生产环境中使用。该功能可能会在未事先通知的情况下发生变化或删除。如果发现 bug,请在 GitHub 上提 [issue](https://github.com/pingcap/tidb/issues) 反馈。 + +`DISTRIBUTE TABLE` 语句用于对指定表的 Region 进行重新打散和调度,以实现表级别的均衡分布。执行该语句可以防止个别 Region 集中在少数 TiFlash 或 TiKV 节点上,从而解决表中 Region 分布不均衡的问题。 + +## 语法图 + +```ebnf+diagram +DistributeTableStmt ::= + "DISTRIBUTE" "TABLE" TableName PartitionNameListOpt "RULE" EqOrAssignmentEq Identifier "ENGINE" EqOrAssignmentEq Identifier "TIMEOUT" EqOrAssignmentEq Identifier + +TableName ::= + (SchemaName ".")? Identifier + +PartitionNameList ::= + "PARTITION" "(" PartitionName ("," PartitionName)* ")" +``` + +## 参数说明 + +通过 `DISTRIBUTE TABLE` 语句重新调度表中的 Region 时,你可以根据需求指定存储引擎(如 TiFlash 或 TiKV)以及不同的 Raft 角色(如 Leader、Learner、Voter)进行均衡。 + +- `RULE`:指定针对哪个 Raft 角色所在的 Region 进行均衡调度,可选值为 `"leader-scatter"`、`"peer-scatter"` 和 `"learner-scatter"`。 +- `ENGINE`:指定存储引擎,可选值为 `"tikv"` 和 `"tiflash"`。 +- `TIMEOUT`:指定打散操作的超时限制。如果 PD 未在该时间内进行打散,打散任务将会自动退出。当未指定该参数时,默认值为 `"30m"`。 + +## 示例 + +对表 `t1` 在 TiKV 上的 Leader 所在的 Region 重新进行均衡调度: + +```sql +CREATE TABLE t1 (a INT); +... +DISTRIBUTE TABLE t1 RULE = "leader-scatter" ENGINE = "tikv" TIMEOUT = "1h"; +``` + +``` ++--------+ +| JOB_ID | ++--------+ +| 100 | ++--------+ +``` + +对表 `t2` 在 TiFlash 上的 Learner 所在的 Region 重新进行均衡调度: + +```sql +CREATE TABLE t2 (a INT); +... +DISTRIBUTE TABLE t2 RULE = "learner-scatter" ENGINE = "tiflash"; +``` + +``` ++--------+ +| JOB_ID | ++--------+ +| 101 | ++--------+ +``` + +对分区表 `t3` 的 `p1` 和 `p2` 分区在 TiKV 上的 Peer 所在的 Region 重新进行均衡调度: + +```sql +CREATE TABLE t3 ( a INT, b INT, INDEX idx(b)) PARTITION BY RANGE( a ) ( + PARTITION p1 VALUES LESS THAN (10000), + PARTITION p2 VALUES LESS THAN (20000), + PARTITION p3 VALUES LESS THAN (MAXVALUE) ); +... +DISTRIBUTE TABLE t3 PARTITION (p1, p2) RULE = "peer-scatter" ENGINE = "tikv"; +``` + +``` ++--------+ +| JOB_ID | ++--------+ +| 102 | ++--------+ +``` + +对分区表 `t4` 的 `p1` 和 `p2` 分区在 TiFlash 上的 Learner 所在的 Region 重新进行均衡调度: + +```sql +CREATE TABLE t4 ( a INT, b INT, INDEX idx(b)) PARTITION BY RANGE( a ) ( + PARTITION p1 VALUES LESS THAN (10000), + PARTITION p2 VALUES LESS THAN (20000), + PARTITION p3 VALUES LESS THAN (MAXVALUE) ); +... +DISTRIBUTE TABLE t4 PARTITION (p1, p2) RULE = "learner-scatter" ENGINE="tiflash"; +``` + +``` ++--------+ +| JOB_ID | ++--------+ +| 103 | ++--------+ +``` + +## 注意事项 + +`DISTRIBUTE TABLE` 语句在重新调度表中的 Region 时,可能会受到 PD 热点调度器的影响。调度完成后,随着时间推移,表的 Region 分布可能再次失衡。 + +## MySQL 兼容性 + +该语句是 TiDB 对 MySQL 语法的扩展。 + +## 另请参阅 + +- [`SHOW DISTRIBUTION JOBS`](/sql-statements/sql-statement-show-distribution-jobs.md) +- [`SHOW TABLE DISTRIBUTION`](/sql-statements/sql-statement-show-table-distribution.md) +- [`SHOW TABLE REGIONS`](/sql-statements/sql-statement-show-table-regions.md) +- [`CANCEL DISTRIBUTION JOB`](/sql-statements/sql-statement-cancel-distribution-job.md) \ No newline at end of file diff --git a/sql-statements/sql-statement-show-distribution-jobs.md b/sql-statements/sql-statement-show-distribution-jobs.md new file mode 100644 index 000000000000..76198998c662 --- /dev/null +++ b/sql-statements/sql-statement-show-distribution-jobs.md @@ -0,0 +1,43 @@ +--- +title: SHOW DISTRIBUTION JOBS +summary: 介绍 TiDB 数据库中 SHOW DISTRIBUTION JOBS 的使用概况。 +--- + +# SHOW DISTRIBUTION JOBS 从 v8.5.4 开始引入 + +`SHOW DISTRIBUTION JOBS` 语句用于显示当前所有的 Region 调度任务。 + +## 语法图 + +```ebnf+diagram +ShowDistributionJobsStmt ::= + "SHOW" "DISTRIBUTION" "JOBS" +``` + +## 示例 + +显示当前所有的 Region 调度任务: + +```sql +SHOW DISTRIBUTION JOBS; +``` + +``` ++--------+----------+-------+----------------+--------+----------------+-----------+---------------------+---------------------+---------------------+ +| Job_ID | Database | Table | Partition_List | Engine | Rule | Status | Create_Time | Start_Time | Finish_Time | ++--------+----------+-------+----------------+--------+----------------+-----------+---------------------+---------------------+---------------------+ +| 100 | test | t1 | NULL | tikv | leader-scatter | finished | 2025-04-24 16:09:55 | 2025-04-24 16:09:55 | 2025-04-24 17:09:59 | +| 101 | test | t2 | NULL | tikv | learner-scatter| cancelled | 2025-05-08 15:33:29 | 2025-05-08 15:33:29 | 2025-05-08 15:33:37 | +| 102 | test | t5 | p1,p2 | tikv | peer-scatter | cancelled | 2025-05-21 15:32:44 | 2025-05-21 15:32:47 | 2025-05-21 15:32:47 | ++--------+----------+-------+----------------+--------+----------------+-----------+---------------------+---------------------+---------------------+ +``` + +## MySQL 兼容性 + +该语句是 TiDB 对 MySQL 语法的扩展。 + +## 另请参阅 + +- [`DISTRIBUTE TABLE`](/sql-statements/sql-statement-distribute-table.md) +- [`SHOW TABLE DISTRIBUTION`](/sql-statements/sql-statement-show-table-distribution.md) +- [`CANCEL DISTRIBUTION JOB`](/sql-statements/sql-statement-cancel-distribution-job.md) \ No newline at end of file diff --git a/sql-statements/sql-statement-show-table-distribution.md b/sql-statements/sql-statement-show-table-distribution.md new file mode 100644 index 000000000000..36ca095aa963 --- /dev/null +++ b/sql-statements/sql-statement-show-table-distribution.md @@ -0,0 +1,60 @@ +--- +title: SHOW TABLE DISTRIBUTION +summary: 介绍 TiDB 数据库中 SHOW TABLE DISTRIBUTION 的使用概况。 +--- + +# SHOW TABLE DISTRIBUTION 从 v8.5.4 开始引入 + +`SHOW TABLE DISTRIBUTION` 语句用于显示指定表的 Region 分布情况。 + +## 语法图 + +```ebnf+diagram +ShowTableDistributionStmt ::= + "SHOW" "TABLE" TableName "DISTRIBUTIONS" + +TableName ::= + (SchemaName ".")? Identifier +``` + +## 示例 + +显示当前表 `t` 的 Region 分布情况: + +```sql +CREATE TABLE `t` ( + `a` int DEFAULT NULL, + `b` int DEFAULT NULL, + KEY `idx` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +PARTITION BY RANGE (`a`) +(PARTITION `p1` VALUES LESS THAN (10000), + PARTITION `p2` VALUES LESS THAN (MAXVALUE)); +SHOW TABLE t DISTRIBUTIONS; +``` + +``` ++----------------+----------+------------+---------------------+-------------------+--------------------+-------------------+--------------------+--------------------------+-------------------------+--------------------------+------------------------+-----------------------+------------------------+ +| PARTITION_NAME | STORE_ID | STORE_TYPE | REGION_LEADER_COUNT | REGION_PEER_COUNT | REGION_WRITE_BYTES | REGION_WRITE_KEYS | REGION_WRITE_QUERY | REGION_LEADER_READ_BYTES | REGION_LEADER_READ_KEYS | REGION_LEADER_READ_QUERY | REGION_PEER_READ_BYTES | REGION_PEER_READ_KEYS | REGION_PEER_READ_QUERY | ++----------------+----------+------------+---------------------+-------------------+--------------------+-------------------+--------------------+--------------------------+-------------------------+--------------------------+------------------------+-----------------------+------------------------+ +| p1 | 1 | tikv | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p1 | 15 | tikv | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p1 | 4 | tikv | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p1 | 5 | tikv | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p1 | 6 | tikv | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p2 | 1 | tikv | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p2 | 15 | tikv | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p2 | 4 | tikv | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p2 | 5 | tikv | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| p2 | 6 | tikv | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ++----------------+----------+------------+---------------------+-------------------+--------------------+-------------------+--------------------+--------------------------+-------------------------+--------------------------+------------------------+-----------------------+------------------------+ +``` + +## MySQL 兼容性 + +该语句是 TiDB 对 MySQL 语法的扩展。 + +## 另请参阅 + +- [`DISTRIBUTE TABLE`](/sql-statements/sql-statement-distribute-table.md) +- [`SHOW DISTRIBUTION JOBS`](/sql-statements/sql-statement-show-distribution-jobs.md) \ No newline at end of file From 0e6bfdb882572fd5fdac087a9268dde5eb900ab6 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 10 Nov 2025 17:38:11 +0800 Subject: [PATCH 07/11] v8.5.4: tidb: change the default value of `tidb_mpp_store_fail_ttl` (#20668) (#21047) --- system-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-variables.md b/system-variables.md index 40d63ce320bd..ba15f363fa8c 100644 --- a/system-variables.md +++ b/system-variables.md @@ -3474,7 +3474,7 @@ v5.0 后,用户仍可以单独修改以上系统变量(会有废弃警告) - 是否持久化到集群:是 - 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 - 类型:Duration -- 默认值:`60s` +- 默认值:`0s`。在 v8.5.3 及之前版本中默认值为 `60s`。 - 刚重启的 TiFlash 可能不能正常提供服务。为了防止查询失败,TiDB 会限制 tidb-server 向刚重启的 TiFlash 节点发送查询。这个变量表示刚重启的 TiFlash 不被发送请求的时间范围。 ### `tidb_multi_statement_mode` 从 v4.0.11 版本开始引入 From c76dfb7b665e260f73af4ce7a8ac6a9b1973d6a3 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Wed, 12 Nov 2025 10:22:37 +0800 Subject: [PATCH 08/11] v8.5.4: reference: update doc for follower read (#20703) (#21041) Co-authored-by: you06 Co-authored-by: Grace Cai --- follower-read.md | 70 +++++++++++++++++++++++++---- grafana-tidb-dashboard.md | 5 +++ media/follower-read/read-index.png | Bin 0 -> 50514 bytes system-variables.md | 4 +- 4 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 media/follower-read/read-index.png diff --git a/follower-read.md b/follower-read.md index 0f957269fccf..7d78b25e95bc 100644 --- a/follower-read.md +++ b/follower-read.md @@ -5,15 +5,26 @@ summary: 了解 Follower Read 的使用与实现。 # Follower Read -当系统中存在读取热点 Region 导致 leader 资源紧张成为整个系统读取瓶颈时,启用 Follower Read 功能可明显降低 leader 的负担,并且通过在多个 follower 之间均衡负载,显著地提升整体系统的吞吐能力。本文主要介绍 Follower Read 的使用方法与实现机制。 +在 TiDB 中,为了保证高可用和数据安全,TiKV 会为每个 Region 保存多个副本,其中一个为 leader,其余为 follower。默认情况下,所有读写请求都由 leader 处理。Follower Read 功能允许在保持强一致性的前提下,从 Region 的 follower 副本读取数据,从而分担 leader 的读取压力,提升集群整体的读吞吐量。 -## 概述 +在执行 Follower Read 时,TiDB 会根据拓扑信息选择合适的副本。具体来说,TiDB 使用 `zone` label 判断一个副本是否为本地副本:当 TiDB 与目标 TiKV 的 `zone` label 相同时,TiDB 认为该副本是本地副本。更多信息参见[通过拓扑 label 进行副本调度](/schedule-replicas-by-topology-labels.md)。 -Follower Read 功能是指在强一致性读的前提下使用 Region 的 follower 副本来承载数据读取的任务,从而提升 TiDB 集群的吞吐能力并降低 leader 负载。Follower Read 包含一系列将 TiKV 读取负载从 Region 的 leader 副本上 offload 到 follower 副本的负载均衡机制。TiKV 的 Follower Read 可以保证数据读取的一致性,可以为用户提供强一致的数据读取能力。 +通过让 follower 参与数据读取,Follower Read 可以实现以下目标: + +- 分散读热点,减轻 leader 负载。 +- 在多可用区或多机房部署中,优先读取本地副本以减少跨区流量。 + +## 适用场景 + +Follower Read 适用于以下场景: + +- 读请求量大、存在明显读热点的业务。 +- 多可用区部署中,希望优先读取本地副本以节省带宽的场景。 +- 在读写分离架构下,希望进一步提升集群整体读性能的场景。 > **注意:** > -> 为了获得强一致读取的能力,在当前的实现中,follower 节点需要向 leader 节点询问当前的执行进度(即 `ReadIndex`),这会产生一次额外的网络请求开销,因此目前 Follower Read 的主要优势是将集群中的读请求与写请求隔离开,并提升整体的读吞吐量。 +> 为确保读取结果的强一致性,Follower Read 在读取前会与 leader 通信以确认当前的提交进度(即执行 Raft `ReadIndex` 操作),该过程会带来一次额外的网络交互。因此,当业务中存在大量读请求或需要读写隔离时,Follower Read 的效果最为显著;但对于低延迟的单次查询,性能提升可能不明显。 ## 使用方式 @@ -29,7 +40,24 @@ set [session | global] tidb_replica_read = '<目标值>'; 默认值:leader -该变量用于设置期待的数据读取方式。 +该变量用于设置期待的数据读取方式。从 v8.5.4 开始,该变量仅对只读 SQL 语句生效。 + +在需要通过读取本地副本以减少跨区流量的场景下,推荐如下配置: + +- `leader`:默认值,提供最佳性能。 +- `closest-adaptive`:在最小性能损失的前提下,尽可能节省跨区流量。 +- `closest-replicas`:可最大限度地节省跨区流量,但可能带来一定的性能损耗。 + +如果当前正在使用其他配置,可参考下表修改为推荐配置: + +| 当前配置 | 推荐配置 | +| ------------- | ------------- | +| `follower` | `closest-replicas` | +| `leader-and-follower` | `closest-replicas` | +| `prefer-leader` | `closest-adaptive` | +| `learner` | `closest-replicas` | + +如果希望使用更精确的读副本选择策略,请参考完整的可选配置列表: - 当设置为默认值 `leader` 或者空字符串时,TiDB 会维持原有行为方式,将所有的读取操作都发送给 leader 副本处理。 - 当设置为 `follower` 时,TiDB 会选择 Region 的 follower 副本完成所有的数据读取操作。当 Region 存在 learner 副本时,TiDB 也会考虑从 learner 副本读取数据,此时 follower 副本和 learner 副本具有相同优先级。若当前 Region 无可用的 follower 副本或 learner 副本,TiDB 会从 leader 副本读取数据。 @@ -52,16 +80,42 @@ set [session | global] tidb_replica_read = '<目标值>'; > - 当 `tidb_replica_read` 设置为 `follower` 且无可用 follower 副本及 learner 副本时,TiDB 会报错。 > - 当 `tidb_replica_read` 设置为 `learner` 且无可用 learner 副本时,TiDB 会报错。 +## 基本监控 + +通过观察 [**TiDB** > **KV Request** > **Read Req Traffic** 面板(从 v8.5.4 开始引入)](/grafana-tidb-dashboard.md#kv-request),可以帮助判断是否需要使用 Follower Read 以及开启 Follower Read 后查看节省流量的效果。 + ## 实现机制 在 Follower Read 功能出现之前,TiDB 采用 strong leader 策略将所有的读写操作全部提交到 Region 的 leader 节点上完成。虽然 TiKV 能够很均匀地将 Region 分散到多个物理节点上,但是对于每一个 Region 来说,只有 leader 副本能够对外提供服务,另外的 follower 除了时刻同步数据准备着 failover 时投票切换成为 leader 外,没有办法对 TiDB 的请求提供任何帮助。 -为了允许在 TiKV 的 follower 节点进行数据读取,同时又不破坏线性一致性和 Snapshot Isolation 的事务隔离,Region 的 follower 节点需要使用 Raft `ReadIndex` 协议确保当前读请求可以读到当前 leader 上已经 commit 的最新数据。在 TiDB 层面,Follower Read 只需根据负载均衡策略将某个 Region 的读取请求发送到 follower 节点。 +Follower Read 包含一系列将 TiKV 读取负载从 Region 的 leader 副本上 offload 到 follower 副本的负载均衡机制。为了允许在 TiKV 的 follower 节点进行数据读取,同时又不破坏线性一致性和 Snapshot Isolation 的事务隔离,Region 的 follower 节点需要使用 Raft `ReadIndex` 协议确保当前读请求可以读到当前 leader 节点上已经 commit 的最新数据。在 TiDB 层面,Follower Read 只需根据负载均衡策略将某个 Region 的读取请求发送到 follower 节点。 ### Follower 强一致读 -TiKV follower 节点处理读取请求时,首先使用 Raft `ReadIndex` 协议与 Region 当前的 leader 进行一次交互,来获取当前 Raft group 最新的 commit index。本地 apply 到所获取的 leader 最新 commit index 后,便可以开始正常的读取请求处理流程。 +TiKV follower 节点处理读取请求时,首先使用 Raft `ReadIndex` 协议与 Region 当前的 leader 节点进行一次交互,来获取当前 Raft group 最新的 commit index(read index)。本地 apply 到所获取的 leader 节点最新 commit index 后,便可以开始正常的读取请求处理流程。 + +![read-index-flow](/media/follower-read/read-index.png) ### Follower 副本选择策略 -由于 TiKV 的 Follower Read 不会破坏 TiDB 的 Snapshot Isolation 事务隔离级别,因此 TiDB 选择 follower 的策略可以采用 round robin 的方式。目前,对于 Coprocessor 请求,Follower Read 负载均衡策略粒度是连接级别的,对于一个 TiDB 的客户端连接在某个具体的 Region 上会固定使用同一个 follower,只有在选中的 follower 发生故障或者因调度策略发生调整的情况下才会进行切换。而对于非 Coprocessor 请求(点查等),Follower Read 负载均衡策略粒度是事务级别的,对于一个 TiDB 的事务在某个具体的 Region 上会固定使用同一个 follower,同样在 follower 发生故障或者因调度策略发生调整的情况下才会进行切换。如果同一事务内既有点查请求又有 Coprocessor 请求,两种请求都将按照上述调度策略分别进行读取,即使 Coprocessor 和点查出现在同一个 Region 上,TiDB 也会当作独立事件来处理。 +Follower Read 不会破坏 TiDB 的 Snapshot Isolation 事务隔离级别。TiDB 在第一次选取副本时会根据 `tidb_replica_read` 的配置进行选择。从第二次重试开始,TiDB 会优先保证读取成功,因此当选中的 follower 节点出现无法访问的故障或其他错误时,会切换到 leader 提供服务。 + +#### `leader` + +- 选择 leader 副本进行读取,不考虑副本位置。 + +#### `closest-replicas` + +- 当和 TiDB 同一个 AZ 的副本是 leader 节点时,不使用 Follower Read。 +- 当和 TiDB 同一个 AZ 的副本不是 leader 节点时,使用 Follower Read。 + +#### `closest-adaptive` + +- 如果预估的返回结果不够大,使用 `leader` 策略,不进行 Follower Read。 +- 如果预估的返回结果足够大,使用 `closest-replicas` 策略。 + +### Follower Read 的性能开销 + +为了保证数据强一致性,Follower Read 不管读取多少数据,都需要执行一次 `ReadIndex` 操作,这将不可避免地消耗更多的 TiKV CPU 资源。因此,在小查询(如点查)场景下,Follower Read 的性能损耗相对更明显。同时,因为对小查询进行本地读取能节省的流量有限,更推荐在大查询或批量读取场景中使用 Follower Read。 + +当 `tidb_replica_read` 设置为 `closest-adaptive` 时,TiDB 在处理小查询时不会使用 Follower Read。因此,在各种工作负载下,相比于 `leader` 策略,TiKV 的额外 CPU 开销通常不超过 10%。 diff --git a/grafana-tidb-dashboard.md b/grafana-tidb-dashboard.md index c8bc4b57f9ae..5b0fd942a653 100644 --- a/grafana-tidb-dashboard.md +++ b/grafana-tidb-dashboard.md @@ -124,6 +124,11 @@ summary: 了解 Grafana Dashboard 中展示的关键指标。 - **cross-zone-out**:尝试在远程可用区执行 Stale Read 的请求的响应的传出流量 - **local-in**:尝试在本地可用区执行 Stale Read 的请求的响应的传入流量 - **local-out**:尝试在本地可用区执行 Stale Read 的请求的响应的传出流量 +- Read Req Traffic + - **leader-local**:Leader Read 在本地可用区处理读请求产生的流量 + - **leader-cross-zone**:Leader Read 在远程可用区处理读请求产生的流量 + - **follower-local**:Follower Read 在本地可用区处理读请求产生的流量 + - **follower-cross-zone**:Follower Read 在远程可用区处理读请求产生的流量 ### PD Client diff --git a/media/follower-read/read-index.png b/media/follower-read/read-index.png new file mode 100644 index 0000000000000000000000000000000000000000..b20a7047f905a9af711f5b740cfc9ac7ec102c9e GIT binary patch literal 50514 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从 v7.4.0 版本开始引入 @@ -5050,7 +5050,7 @@ Query OK, 0 rows affected, 1 warning (0.00 sec) ### `tidb_slow_txn_log_threshold` 从 v7.0.0 版本开始引入 - 作用域:SESSION -- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 +- 是否受 Hint [SET_VAR](/optimizer-hints.md#set_varvar_namevar_value) 控制:否 - 类型:无符号整数型 - 默认值:`0` - 范围:`[0, 9223372036854775807]` From 4b7bfbc992bdd7d36f4c78983930cce60a8f41e3 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Tue, 18 Nov 2025 15:24:37 +0800 Subject: [PATCH 09/11] cdc: update description about new architecture (#21055) (#21086) --- ticdc/monitor-ticdc.md | 4 ++- ticdc/ticdc-architecture.md | 59 ++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/ticdc/monitor-ticdc.md b/ticdc/monitor-ticdc.md index 7b7ec7a9afad..1ee3d25cddcf 100644 --- a/ticdc/monitor-ticdc.md +++ b/ticdc/monitor-ticdc.md @@ -15,7 +15,9 @@ cdc cli changefeed create --server=http://10.0.10.25:8300 --sink-uri="mysql://ro ## TiCDC 新架构监控指标 -[TiCDC 新架构](/ticdc/ticdc-architecture.md)的监控面板 **TiCDC-New-Arch** 暂时未集成到 TiUP 中。要在 Grafana 中查看相关监控信息,你需要手动导入 TiCDC 监控指标文件: +[TiCDC 新架构](/ticdc/ticdc-architecture.md)的监控面板为 **TiCDC-New-Arch**。对于 v8.5.4 及以上版本的 TiDB 集群,该监控面板已在集群部署或升级时自动集成到 Grafana 中,无需手动操作。 + +如果你的集群版本低于 v8.5.4,需要手动导入 TiCDC 监控指标文件: 1. 下载 TiCDC 新架构监控指标文件 diff --git a/ticdc/ticdc-architecture.md b/ticdc/ticdc-architecture.md index a2a0d1e818ec..8dda1eb38804 100644 --- a/ticdc/ticdc-architecture.md +++ b/ticdc/ticdc-architecture.md @@ -5,7 +5,9 @@ summary: 介绍 TiCDC 新架构的主要特性、架构设计、升级部署指 # TiCDC 新架构 -从 [TiCDC v8.5.4-release.1](https://github.com/pingcap/ticdc/releases/tag/v8.5.4-release.1) 版本起,TiCDC 引入新架构,显著提升了实时数据复制的性能、可扩展性与稳定性,同时降低了资源成本。新架构重新设计了 TiCDC 的核心组件并优化了数据处理流程,具有以下优势: +从 [TiCDC v8.5.4-release.1](https://github.com/pingcap/ticdc/releases/tag/v8.5.4-release.1) 版本起,TiCDC 引入新架构,显著提升了实时数据复制的性能、可扩展性与稳定性,同时降低了资源成本。 + +新架构在完全兼容 [TiCDC 老架构](/ticdc/ticdc-classic-architecture.md)的配置项、使用方式和 API 的基础上,对 TiCDC 核心组件与数据处理流程进行了重构与优化,具有以下优势: - **更高的单节点性能**:单节点可支持最多 50 万张表的同步任务,宽表场景下单节点同步流量最高可达 190 MiB/s。 - **更强的扩展能力**:集群同步能力接近线性扩展,单集群可扩展至超过 100 个节点,支持超 1 万个 Changefeed;单个 Changefeed 可支持百万级表的同步任务。 @@ -106,16 +108,57 @@ TiCDC 新架构仅支持 v7.5.0 或者以上版本的 TiDB 集群,使用之前 你可以通过 TiUP 或 TiDB Operator 部署 TiCDC 新架构。 +### 部署启用新架构 TiCDC 的全新 TiDB 集群 + + +
+ +使用 TiUP 部署 v8.5.4 或者以上版本的全新 TiDB 集群时,可以同时部署启用新架构的 TiCDC 组件。你需要在 TiUP 启动 TiDB 集群时的配置文件中添加 TiCDC 组件相关配置,并设置 `newarch: true` 以启用新架构,以下是一个示例: + +```yaml +cdc_servers: + - host: 10.0.1.20 + config: + newarch: true + - host: 10.0.1.21 + config: + newarch: true +``` + +更多 TiCDC 部署信息,请参考[使用 TiUP 部署包含 TiCDC 组件的全新 TiDB 集群](/ticdc/deploy-ticdc.md#使用-tiup-部署包含-ticdc-组件的全新-tidb-集群)。 + +
+
+ +使用 TiDB Operator 部署 v8.5.4 或者以上版本的全新 TiDB 集群时,可以同时部署启用新架构的 TiCDC 组件。你需要在集群配置文件中添加 TiCDC 组件配置,并设置 `newarch = true` 以启用新架构,以下是一个示例: + +```yaml +spec: + ticdc: + baseImage: pingcap/ticdc + version: v8.5.4 + replicas: 3 + config: + newarch = true +``` + +更多 TiCDC 部署信息,请参考[全新部署 TiDB 集群同时部署 TiCDC](https://docs.pingcap.com/zh/tidb-in-kubernetes/stable/deploy-ticdc/#在现有-tidb-集群上新增-ticdc-组件)。 + +
+
+ +### 在原有 TiDB 集群中部署启用新架构的 TiCDC 组件 +
-以下为通过 TiUP 部署 TiCDC 新架构的步骤: +以下为通过 TiUP 在原有 TiDB 集群中部署启用新架构的 TiCDC 组件的步骤: 1. 如果你的 TiDB 集群中尚无 TiCDC 节点,参考[扩容 TiCDC 节点](/scale-tidb-using-tiup.md#扩容-ticdc-节点)在集群中扩容新的 TiCDC 节点,否则跳过该步骤。 -2. 下载 TiCDC 新架构离线包。 +2. 如果你的 TiDB 集群为 v8.5.4 之前版本,需要按照以下方式手动下载 TiCDC 新架构离线包,并将下载的 TiCDC 二进制文件动态替换到你的 TiDB 集群,否则跳过该步骤。 - 离线包下载链接格式为 `https://tiup-mirrors.pingcap.com/cdc-${version}-${os}-${arch}.tar.gz`。其中,`${version}` 为 TiCDC 版本号,`${os}` 为你的操作系统,`${arch}` 为组件运行的平台(`amd64` 或 `arm64`)。 + 离线包下载链接格式为 `https://tiup-mirrors.pingcap.com/cdc-${version}-${os}-${arch}.tar.gz`。其中,`${version}` 为 TiCDC 版本号(版本号信息可参考 [TiCDC 新架构版本发布列表](https://github.com/pingcap/ticdc/releases)),`${os}` 为你的操作系统,`${arch}` 为组件运行的平台(`amd64` 或 `arm64`)。 例如,可以使用以下命令下载 Linux 系统 x86-64 架构的 TiCDC v8.5.4-release.1 的离线包: @@ -158,9 +201,9 @@ TiCDC 新架构仅支持 v7.5.0 或者以上版本的 TiDB 集群,使用之前
-以下为通过 TiDB Operator 部署 TiCDC 新架构的步骤: +以下为通过 TiDB Operator 在原有 TiDB 集群中部署启用新架构的 TiCDC 组件的步骤: -- 如果现有 TiDB 集群中没有 TiCDC 组件,参考[在现有 TiDB 集群上新增 TiCDC 组件](https://docs.pingcap.com/zh/tidb-in-kubernetes/stable/deploy-ticdc/#在现有-tidb-集群上新增-ticdc-组件)在集群中扩容新的 TiCDC 节点。操作时,只需在集群配置文件中将 TiCDC 的镜像版本指定为新架构版本即可。 +- 如果现有 TiDB 集群中没有 TiCDC 组件,参考[在现有 TiDB 集群上新增 TiCDC 组件](https://docs.pingcap.com/zh/tidb-in-kubernetes/stable/deploy-ticdc/#在现有-tidb-集群上新增-ticdc-组件)在集群中添加新的 TiCDC 节点。操作时,只需在集群配置文件中将 TiCDC 的镜像版本指定为新架构版本即可。版本号信息可参考 [TiCDC 新架构版本发布列表](https://github.com/pingcap/ticdc/releases)。 示例如下: @@ -239,6 +282,6 @@ cdc cli changefeed query -s --server=http://127.0.0.1:8300 --changefeed-id=simpl ## 监控 -目前,TiUP 尚未集成 TiCDC 新架构的监控面板 **TiCDC-New-Arch**。要在 Grafana 中查看该面板,你需要手动导入 [TiCDC 监控指标文件](https://github.com/pingcap/ticdc/blob/master/metrics/grafana/ticdc_new_arch.json)。 +TiCDC 新架构的监控面板为 **TiCDC-New-Arch**。对于 v8.5.4 及以上版本的 TiDB 集群,该监控面板已在集群部署或升级时自动集成到 Grafana 中,无需手动操作。如果你的集群版本低于 v8.5.4,需手动导入 [TiCDC 监控指标文件](https://github.com/pingcap/ticdc/blob/master/metrics/grafana/ticdc_new_arch.json) 以启用监控。 -各监控指标的详细说明,请参考 [TiCDC 新架构监控指标](/ticdc/monitor-ticdc.md#ticdc-新架构监控指标)。 \ No newline at end of file +导入步骤以及各监控指标的详细说明,请参考 [TiCDC 新架构监控指标](/ticdc/monitor-ticdc.md#ticdc-新架构监控指标)。 From e253876280bda3d4ef22f6a25857bbdd68775d0a Mon Sep 17 00:00:00 2001 From: Grace Cai Date: Wed, 19 Nov 2025 14:58:43 +0800 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: xixirangrang --- ddl_embedded_analyze.md | 2 +- system-variables.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ddl_embedded_analyze.md b/ddl_embedded_analyze.md index 76ad8d2535ef..bbabba635759 100644 --- a/ddl_embedded_analyze.md +++ b/ddl_embedded_analyze.md @@ -3,7 +3,7 @@ title: 内嵌于 DDL 的 Analyze summary: 本文介绍内嵌于新建或重组索引的 DDL 中的 Analyze 特性,用于确保新索引的统计信息及时更新。 --- -# 内嵌于 DDL 的 Analyze 从 v8.5.4 和 v9.0.0 开始引入 +# 内嵌于 DDL 的 Analyze 从 v8.5.4 本文介绍内嵌于以下两类 DDL 的 Analyze 特性: diff --git a/system-variables.md b/system-variables.md index c210603e2d8e..61e32c9d59bc 100644 --- a/system-variables.md +++ b/system-variables.md @@ -1446,7 +1446,7 @@ mysql> SELECT job_info FROM mysql.analyze_jobs ORDER BY end_time DESC LIMIT 1; > > * 在升级到 v6.5.0 及以上版本时,请确保 TiDB 的 [`temp-dir`](/tidb-configuration-file.md#temp-dir-从-v630-版本开始引入) 路径已正确挂载了 SSD 磁盘,并确保运行 TiDB 的操作系统用户对该目录有读写权限,否则在运行时可能产生不可预知的问题。该参数是 TiDB 的配置参数,设置后需要重启 TiDB 才能生效。因此,在升级前提前进行设置,可以避免再次重启。 -### `tidb_stats_update_during_ddl` 从 v8.5.4 和 v9.0.0 版本开始引入 +### `tidb_stats_update_during_ddl` 从 v8.5.4 版本开始引入 - 作用域:SESSION | GLOBAL - 是否持久化到集群:是 From a6005501442c672515a1f915451b31adfe003f72 Mon Sep 17 00:00:00 2001 From: Grace Cai Date: Wed, 19 Nov 2025 14:59:34 +0800 Subject: [PATCH 11/11] Update ddl_embedded_analyze.md --- ddl_embedded_analyze.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddl_embedded_analyze.md b/ddl_embedded_analyze.md index bbabba635759..3a21fd0fb01d 100644 --- a/ddl_embedded_analyze.md +++ b/ddl_embedded_analyze.md @@ -3,7 +3,7 @@ title: 内嵌于 DDL 的 Analyze summary: 本文介绍内嵌于新建或重组索引的 DDL 中的 Analyze 特性,用于确保新索引的统计信息及时更新。 --- -# 内嵌于 DDL 的 Analyze 从 v8.5.4 +# 内嵌于 DDL 的 Analyze 从 v8.5.4 开始引入 本文介绍内嵌于以下两类 DDL 的 Analyze 特性: