diff --git a/TOC.md b/TOC.md index 4020a92271b6..c130c6306893 100644 --- a/TOC.md +++ b/TOC.md @@ -978,6 +978,7 @@ - [下推到 TiKV 的表达式列表](/functions-and-operators/expressions-pushed-down.md) - [Oracle 与 TiDB 函数和语法差异对照](/oracle-functions-to-tidb.md) - [聚簇索引](/clustered-indexes.md) + - [全局索引](/global-indexes.md) - [向量索引](/vector-search/vector-search-index.md) - [约束](/constraints.md) - [生成列](/generated-columns.md) diff --git a/basic-features.md b/basic-features.md index 69eb6dcbf867..90de08316cd5 100644 --- a/basic-features.md +++ b/basic-features.md @@ -64,7 +64,7 @@ aliases: ['/docs-cn/dev/basic-features/','/docs-cn/dev/experimental-features-4.0 | [多值索引](/sql-statements/sql-statement-create-index.md#多值索引) | Y | Y | Y | Y | N | N | N | | [外键约束](/foreign-key.md) | Y | E | E | E | N | N | N | | [TiFlash 延迟物化](/tiflash/tiflash-late-materialization.md) | Y | Y | Y | Y | N | N | N | -| [全局索引 (Global Index)](/partitioned-table.md#全局索引) | Y | N | N | N | N | N | N | +| [全局索引 (Global Index)](/global-indexes.md) | Y | N | N | N | N | N | N | | [向量索引](/vector-search/vector-search-index.md) | E | N | N | N | N | N | N | ## SQL 语句 @@ -172,7 +172,7 @@ aliases: ['/docs-cn/dev/basic-features/','/docs-cn/dev/experimental-features-4.0 | [Range INTERVAL 分区](/partitioned-table.md#range-interval-分区) | Y | Y | Y | Y | E | N | N | | [分区表转换为非分区表](/partitioned-table.md#将分区表转换为非分区表) | Y | Y | Y | N | N | N | N | | [对现有表进行分区](/partitioned-table.md#对现有表进行分区) | Y | Y | Y | N | N | N | N | -| [全局索引 (Global Index)](/partitioned-table.md#全局索引) | Y | N | N | N | N | N | N | +| [全局索引 (Global Index)](/global-indexes.md) | Y | N | N | N | N | N | N | ## 统计信息 diff --git a/best-practices/tidb-best-practices.md b/best-practices/tidb-best-practices.md index 03c9a61d7fb1..b51e8d4de502 100644 --- a/best-practices/tidb-best-practices.md +++ b/best-practices/tidb-best-practices.md @@ -84,7 +84,7 @@ TiDB 支持完整的二级索引,并且是全局索引,很多查询可以通 + 通过索引查询和直接扫描 Table 的区别 - TiDB 实现了全局索引,所以索引和 Table 中的数据并不一定在一个数据分片上。通过索引查询的时候,需要先扫描索引,得到对应的行 ID,然后通过行 ID 去取数据,所以可能会涉及到两次网络请求,会有一定的性能开销。 + TiDB 实现了[全局索引](/global-indexes.md),所以索引和 Table 中的数据并不一定在一个数据分片上。通过索引查询的时候,需要先扫描索引,得到对应的行 ID,然后通过行 ID 去取数据,所以可能会涉及到两次网络请求,会有一定的性能开销。 如果查询涉及到大量的行,那么扫描索引是并发进行,只要第一批结果已经返回,就可以开始去取 Table 的数据,所以这里是一个并行 + Pipeline 的模式,虽然有两次访问的开销,但是延迟并不会很大。 diff --git a/choose-index.md b/choose-index.md index ac5b53a5e92d..2b87e71de663 100644 --- a/choose-index.md +++ b/choose-index.md @@ -82,7 +82,7 @@ Skyline-Pruning 是一个针对索引的启发式过滤规则,能降低错误 - 选择该索引是否能满足一定的顺序。因为索引的读取可以保证某些列集合的顺序,所以满足查询要求顺序的索引在这个维度上优于不满足的索引。 -- 该索引是否为[全局索引](/partitioned-table.md#全局索引)。在分区表中,全局索引相比普通索引能有效的降低一个 SQL 的 cop task 数量,进而提升整体性能。 +- 该索引是否为[全局索引](/global-indexes.md)。在分区表中,全局索引相比普通索引能有效的降低一个 SQL 的 cop task 数量,进而提升整体性能。 对于上述维度,如果索引 `idx_a` 在这四个维度上都不比 `idx_b` 差,且有一个维度比 `idx_b` 好,那么 TiDB 会优先选择 `idx_a`。在执行 `EXPLAIN FORMAT = 'verbose' ...` 语句时,如果 Skyline-Pruning 排除了某些索引,TiDB 会输出一条 NOTE 级别的 warning 提示哪些索引在 Skyline-Pruning 排除之后保留下来。 diff --git a/global-indexes.md b/global-indexes.md new file mode 100644 index 000000000000..77f9ebddeed7 --- /dev/null +++ b/global-indexes.md @@ -0,0 +1,316 @@ +--- +title: 全局索引 +summary: 介绍 TiDB 全局索引的适用场景、优势、使用方法、工作原理及其限制等。 +--- + +# 全局索引 + +在引入全局索引 (Global Index) 之前,TiDB 会为每个分区创建一个本地索引 (Local Index),即一个分区对应一个本地索引。这种索引方式存在一个[使用限制](/partitioned-table.md#分区键主键和唯一键):主键和唯一键必须包含所有的分区键,以确保数据的全局唯一性。此外,当查询的数据跨越多个分区时,TiDB 需要扫描各个分区的数据才能返回结果。 + +为解决这些问题,TiDB 从 [v8.3.0](/releases/release-8.3.0.md) 开始引入全局索引。全局索引能覆盖整个表的数据,使得主键和唯一键在不包含分区键的情况下仍能保持全局唯一性。此外,全局索引可以在一次操作中访问多个分区的索引数据,而无需对每个分区的本地索引逐一查找,显著提升了针对非分区键的查询性能。 + +## 优势 + +全局索引可以显著提升查询性能,增强索引设计灵活性,降低数据迁移和应用修改成本。 + +### 提升查询性能 + +全局索引能够有效提高检索非分区列的效率。当查询涉及非分区列时,全局索引可以快速定位相关数据,避免了对所有分区的全表扫描,可以显著降低协处理任务 (Coprocessor Task) 的数量,这对于分区数量庞大的场景尤为有效。 + +经过测试,在分区数量为 100 的情况下,sysbench `select_random_points` 场景的性能提升了 53 倍。 + +### 增强索引设计灵活性 + +全局索引消除了分区表中唯一键必须包含所有分区列的限制。这让你在设计索引时更加灵活,可以根据实际的查询需求和业务逻辑来创建索引,而不再受限于表的分区方案。这种灵活性不仅有助于优化查询性能,还能更好地满足多样化的业务需求。 + +### 降低数据迁移和应用修改成本 + +在数据迁移和应用修改过程中,全局索引可以显著减少额外的调整工作。如果没有全局索引,你可能需要更改分区方案或者重写查询语句,以适应索引的限制。而使用全局索引之后,可以避免这些修改,从而降低开发和维护成本。 + +例如,将 Oracle 数据库中的某张表迁移到 TiDB 时,因为 Oracle 支持全局索引,某些表中可能存在一些不包含分区列的唯一索引。在 TiDB 引入全局索引之前,你需要调整表结构以适应 TiDB 的分区表限制。在 TiDB 支持全局索引后,你只需在迁移时简单地修改索引定义,将其设置为全局索引,即可与 Oracle 保持一致,从而大幅降低迁移成本。 + +## 使用限制 + +- 如果索引定义中未显式指定 `GLOBAL` 关键字,TiDB 将默认创建本地索引 (Local Index)。 +- `GLOBAL` 和 `LOCAL` 关键字仅适用于分区表,对非分区表没有影响。即在非分区表中,全局索引和本地索引之间没有区别。 +- 以下 DDL 操作会触发全局索引的更新:`DROP PARTITION`、`TRUNCATE PARTITION` 和 `REORGANIZE PARTITION`。这些 DDL 需等待全局索引更新完成后才会返回结果,因此耗时会相应增加。尤其是在数据归档场景下,如 `DROP PARTITION` 和 `TRUNCATE PARTITION`,如果没有全局索引,通常可以立即完成;但使用全局索引后,耗时会随着需要更新的索引数量的增加而增加。 +- 包含全局索引的表不支持 `EXCHANGE PARTITION`。 +- 默认情况下,分区表的主键为聚簇索引,且必须包含分区键。如果要求主键不包含分区建,可以在建表时显式指定主键为非聚簇的全局索引,例如:`PRIMARY KEY(col1, col2) NONCLUSTERED GLOBAL`。 +- 如果全局索引建立在表达式列上,或者全局索引同时也是前缀索引(例如 `UNIQUE KEY idx_id_prefix (id(10)) GLOBAL`),则需要手动为该全局索引收集统计信息。 + +## 功能演进 + +- **v7.6.0 之前**:TiDB 仅支持分区表的本地索引。这意味着,分区表上的唯一键必须包含分区表达式中的所有列。如果查询条件中没有使用分区键,则查询需要扫描所有分区,导致查询性能下降。 +- **[v7.6.0](/releases/release-7.6.0.md)**:引入了系统变量 [`tidb_enable_global_index`](/system-variables.md#tidb_enable_global_index-从-v760-版本开始引入),用于开启全局索引功能。然而,当时该功能仍在开发中,不推荐启用。 +- **[v8.3.0](/releases/release-8.3.0.md)**:全局索引作为实验特性发布。你可以在创建索引时使用 `GLOBAL` 关键字来显式创建全局索引。 +- **[v8.4.0](/releases/release-8.4.0.md)**:全局索引功能正式发布 (GA)。你可以直接使用 `GLOBAL` 关键字创建全局索引,无需再设置系统变量 `tidb_enable_global_index`。从该版本开始,该系统变量被废弃,且变量值固定为 `ON`,即默认启用全局索引。 +- **[v8.5.0](/releases/release-8.5.0.md)**:全局索引支持包含分区表达式中的所有列。 + +## 全局索引和本地索引 + +下图展示了本地索引和全局索引的区别。 + +Global Index vs. Local Index + +**全局索引的适用场景**: + +- **数据归档不频繁**:例如,医疗行业的部分业务数据需要保存 30 年,通常按月分区,然后一次性创建 360 个分区,且很少进行 `DROP` 或 `TRUNCATE` 操作。在这种情况下,使用全局索引更为合适,能够提供跨分区的一致性和查询性能。 +- **查询需要跨分区的数据**:当查询需要访问多个分区的数据时,全局索引可以避免跨分区扫描,提高查询效率。 + +**本地索引的适用场景**: + +- **数据归档需求**:如果数据归档操作很频繁,且主要查询集中在单个分区内,本地索引可以提供更好的性能。 +- **需要使用分区交换功能**:在银行等行业,可能会将处理后的数据先写入普通表,确认无误后再交换到分区表,以减少对分区表性能的影响。此时,本地索引更为适用,因为一旦使用了全局索引,分区表将不再支持分区交换功能。 + +## 全局索引和聚簇索引 + +由于聚簇索引和全局索引的原理限制,一个索引不能同时作为聚簇索引和全局索引。然而,这两种索引在不同查询场景中能提供不同的性能优化。在遇到需要同时兼顾两者的需求时,你可以将分区列添加到聚簇索引中,同时创建一个不包含分区列的全局索引。 + +假设有如下表结构: + +```sql +CREATE TABLE `t` ( + `id` int DEFAULT NULL, + `ts` timestamp NULL DEFAULT NULL, + `data` varchar(100) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +PARTITION BY RANGE (UNIX_TIMESTAMP(`ts`)) +(PARTITION `p0` VALUES LESS THAN (1735660800) + PARTITION `p1` VALUES LESS THAN (1738339200) + ...) +``` + +在上面的 `t` 表中,`id` 列的值是唯一的。为了优化点查和范围查询的性能,可以选择在建表语句中定义一个聚簇索引 `PRIMARY KEY(id, ts)` 和一个不包含分区列的全局索引 `UNIQUE KEY id(id)`。这样在进行基于 `id` 的点查询时,会采用全局索引 `id`,选择 `PointGet` 的执行计划;而在进行范围查询时,聚簇索引则会被选中,因为聚簇索引相比全局索引少了一次回表操作,从而提升查询效率。 + +修改后的表结构如下所示: + +```sql +CREATE TABLE `t` ( + `id` int NOT NULL, + `ts` timestamp NOT NULL, + `data` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`, `ts`) /*T![clustered_index] CLUSTERED */, + UNIQUE KEY `id` (`id`) /*T![global_index] GLOBAL */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +PARTITION BY RANGE (UNIX_TIMESTAMP(`ts`)) +(PARTITION `p0` VALUES LESS THAN (1735660800), + PARTITION `p1` VALUES LESS THAN (1738339200) + ...) +``` + +这种方式既能优化基于 `id` 的点查询,又能提升范围查询的性能,同时确保表的分区列在基于时间戳的查询中能得到有效利用。 + +## 使用方法 + +如果你需要创建全局索引,则在索引定义中添加 `GLOBAL` 关键字。 + +> **注意:** +> +> 全局索引会影响分区管理。执行 `DROP`、`TRUNCATE` 和 `REORGANIZE PARTITION` 操作会触发表级别全局索引的更新,这意味着这些 DDL 操作只有在对应表的全局索引更新完成后才会返回结果,耗时可能会增加。 + +```sql +CREATE TABLE t1 ( + col1 INT NOT NULL, + col2 DATE NOT NULL, + col3 INT NOT NULL, + col4 INT NOT NULL, + UNIQUE KEY uidx12(col1, col2) GLOBAL, + UNIQUE KEY uidx3(col3), + KEY idx1(col1) GLOBAL +) +PARTITION BY HASH(col3) +PARTITIONS 4; +``` + +在上述示例中,唯一索引 `uidx12` 和非唯一索引 `idx1` 将成为全局索引,但 `uidx3` 仍是常规的唯一索引。 + +请注意,**聚簇索引**不能成为全局索引,示例如下: + +```sql +CREATE TABLE t2 ( + col1 INT NOT NULL, + col2 DATE NOT NULL, + PRIMARY KEY (col2) CLUSTERED GLOBAL +) PARTITION BY HASH(col1) PARTITIONS 5; +``` + +``` +ERROR 1503 (HY000): A CLUSTERED INDEX must include all columns in the table's partitioning function +``` + +聚簇索引不能同时作为全局索引。原因在于,如果聚簇索引是全局索引,则表将不再分区。聚簇索引的键是分区级别的行数据的键,但全局索引是表级别的,这就产生了冲突。如果需要将主键设置为全局索引,则需要显式设置该主键为非聚簇索引,如 `PRIMARY KEY(col1, col2) NONCLUSTERED GLOBAL`。 + +你可以通过 [`SHOW CREATE TABLE`](/sql-statements/sql-statement-show-create-table.md) 输出中的 `GLOBAL` 索引选项来识别全局索引。 + +```sql +SHOW CREATE TABLE t1\G +``` + +``` + Table: t1 +Create Table: CREATE TABLE `t1` ( + `col1` int NOT NULL, + `col2` date NOT NULL, + `col3` int NOT NULL, + `col4` int NOT NULL, + UNIQUE KEY `uidx12` (`col1`,`col2`) /*T![global_index] GLOBAL */, + 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) +``` + +或查询 [`INFORMATION_SCHEMA.TIDB_INDEXES`](/information-schema/information-schema-tidb-indexes.md) 表并查看输出中的 `IS_GLOBAL` 列来识别全局索引。 + +```sql +SELECT * FROM information_schema.tidb_indexes WHERE table_name='t1'; +``` + +``` ++--------------+------------+------------+----------+--------------+-------------+----------+---------------+------------+----------+------------+-----------+-----------+ +| TABLE_SCHEMA | TABLE_NAME | NON_UNIQUE | KEY_NAME | SEQ_IN_INDEX | COLUMN_NAME | SUB_PART | INDEX_COMMENT | Expression | INDEX_ID | IS_VISIBLE | CLUSTERED | IS_GLOBAL | ++--------------+------------+------------+----------+--------------+-------------+----------+---------------+------------+----------+------------+-----------+-----------+ +| 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, idx1 LOCAL); +``` + +## 工作原理 + +本节介绍全局索引的工作机制,包括其设计理念与编码方式。 + +### 设计理念 + +在 TiDB 的分区表中,本地索引的键值前缀是分区表的 ID,而全局索引的前缀是表的 ID。这样的改进能够确保全局索引的数据在 TiKV 上连续分布,从而减少查询索引时的 RPC 请求数量。 + +```sql +CREATE TABLE `sbtest` ( + `id` int(11) NOT NULL, + `k` int(11) NOT NULL DEFAULT '0', + `c` char(120) NOT NULL DEFAULT '', + KEY idx(k), + KEY global_idx(k) GLOBAL +) partition by hash(id) partitions 5; +``` + +以上述表结构为例,`idx` 为普通索引,`global_idx` 为全局索引。索引 `idx` 的数据会分布在 5 个不同的 Range 中,如 `PartitionID1_i_xxx`、`PartitionID2_i_xxx` 等,而索引 `global_idx` 的数据则会集中在一个 Range (`TableID_i_xxx`) 内。 + +当执行与 `k` 相关的查询时,如 `SELECT * FROM sbtest WHERE k > 1`,通过普通索引 `idx` 会构造 5 个不同的 Range,而通过全局索引 `global_idx` 则只会构造 1 个 Range。每个 Range 在 TiDB 中对应一个或多个 RPC 请求。因此,使用全局索引可以降低数倍的 RPC 请求数,从而提升查询索引的性能。 + +下图直观地展示了在使用 `idx` 和 `global_idx` 两个不同索引执行 `SELECT * FROM sbtest WHERE k > 1` 查询语句时,在 RPC 请求和数据流转过程中的差异: + +![Mechanism of Global Indexes](/media/global-index-mechanism.png) + +### 编码方式 + +在 TiDB 中,索引项被编码为键值对。对于分区表,每个分区在 TiKV 层被视为一个独立的物理表,拥有自己的 `partitionID`。因此,分区表的索引项编码如下: + +``` +唯一键 +Key: +- PartitionID_indexID_ColumnValues + +Value: +- IntHandle + - TailLen_IntHandle + +- CommonHandle + - TailLen_IndexVersion_CommonHandle + +非唯一键 +Key: +- PartitionID_indexID_ColumnValues_Handle + +Value: +- IntHandle + - TailLen_Padding + +- CommonHandle + - TailLen_IndexVersion +``` + +在全局索引中,索引项的编码方式有所不同。为了使全局索引的键布局与当前索引键编码保持兼容,新的索引编码布局如下: + +``` +唯一键 +Key: +- TableID_indexID_ColumnValues + +Value: +- IntHandle + - TailLen_PartitionID_IntHandle + +- CommonHandle + - TailLen_IndexVersion_CommonHandle_PartitionID + +非唯一键 +Key: +- TableID_indexID_ColumnValues_Handle + +Value: +- IntHandle + - TailLen_PartitionID + +- CommonHandle + - TailLen_IndexVersion_PartitionID +``` + +这种编码方式使得全局索引的键以 `TableID` 开头,而 `PartitionID` 被放置在 Value 中。这样设计的优点是与现有的索引键编码方式兼容。然而,这也带来了一些挑战,例如在执行 `DROP PARTITION`、`TRUNCATE PARTITION` 等 DDL 操作时,由于索引项不连续,需要进行额外处理。 + +## 性能测试数据 + +以下测试基于 sysbench 的 `select_random_points` 场景,主要用于对比不同分区策略与索引方式下的查询性能。 + +测试表的表结构如下: + +```sql +CREATE TABLE `sbtest` ( + `id` int(11) NOT NULL, + `k` int(11) NOT NULL DEFAULT '0', + `c` char(120) NOT NULL DEFAULT '', + `pad` char(60) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */, + KEY `k_1` (`k`) + /* Key `k_1` (`k`, `c`) GLOBAL */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +/* Partition by hash(`id`) partitions 100 */ +/* Partition by range(`id`) xxxx */ +``` + +负载 SQL 如下: + +```sql +SELECT id, k, c, pad +FROM sbtest1 +WHERE k IN (xx, xx, xx) +``` + +Range Partition (100 partitions): + +| Table type | Concurrency 1 | Concurrency 32 | Concurrency 64 | Average RU | +| --------------------------------------------------------------------- | ------------- | -------------- | -------------- | ---------- | +| Clustered non-partitioned table | 225 | 19,999 | 30,293 | 7.92 | +| Clustered table range partitioned by PK | 68 | 480 | 511 | 114.87 | +| Clustered table range partitioned by PK, with Global Index on `k`,`c` | 207 | 17,798 | 27,707 | 11.73 | + +Hash Partition (100 partitions): + +| Table type | Concurrency 1 | Concurrency 32 | Concurrency 64 | Average RU | +| -------------------------------------------------------------------- | ------------- | -------------- | -------------- | ---------- | +| Clustered non-partitioned table | 166 | 20,361 | 28,922 | 7.86 | +| Clustered table hash partitioned by PK | 60 | 244 | 283 | 119.73 | +| Clustered table hash partitioned by PK, with Global Index on `k`,`c` | 156 | 18,233 | 15,581 | 10.77 | + +通过上述测试可以看出,在高并发环境下,全局索引能够显著提升分区表查询性能,提升幅度可达 50 倍。同时,全局索引还能够显著降低资源 (RU) 消耗。随着分区数量的增加,这种性能提升的效果将愈加明显。 diff --git a/media/global-index-mechanism.png b/media/global-index-mechanism.png new file mode 100644 index 000000000000..3438b236f589 Binary files /dev/null and b/media/global-index-mechanism.png differ diff --git a/media/global-index-vs-local-index.png b/media/global-index-vs-local-index.png new file mode 100644 index 000000000000..a84c65a1214d Binary files /dev/null and b/media/global-index-vs-local-index.png differ diff --git a/partitioned-table.md b/partitioned-table.md index 218b1b71d4cb..8d11193900c9 100644 --- a/partitioned-table.md +++ b/partitioned-table.md @@ -1176,7 +1176,7 @@ ALTER TABLE member_level PARTITION BY RANGE(level) PARTITION pMax VALUES LESS THAN (MAXVALUE)); ``` -对普通表进行分区或者对分区表进行重新分区时,可以根据需要将索引更新为全局索引或普通索引: +对普通表进行分区或者对分区表进行重新分区时,可以根据需要将索引更新为[全局索引](/global-indexes.md)或普通索引: ```sql CREATE TABLE t1 ( @@ -1474,7 +1474,7 @@ SELECT store_id, COUNT(department_id) AS c > **注意:** > -> 使用[全局索引](#全局索引)时,可以忽略该规则。 +> 使用[全局索引](/global-indexes.md)时,可以忽略该规则。 这里所指的唯一也包含了主键,因为根据主键的定义,主键必须是唯一的。例如,下面这些建表语句就是无效的: @@ -1685,106 +1685,6 @@ 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 开始引入全局索引。全局索引能覆盖整个表的数据,使得主键和唯一键在不包含分区键的情况下仍能保持全局唯一性。此外,全局索引可以在一次操作中访问多个分区的索引数据,而无需对每个分区的局部索引逐一查找,显著提升了针对非分区键的查询性能。从 v9.0.0 开始,非唯一索引也可以创建为全局索引。 - -如果你需要创建全局索引,可以通过在索引定义中添加 `GLOBAL` 关键字来实现。 - -> **注意:** -> -> 全局索引对分区管理有影响,执行 `DROP`、`TRUNCATE` 和 `REORGANIZE PARTITION` 操作也会触发表级别全局索引的更新,这意味着这些 DDL 操作只有在对应表的全局索引完全更新后才会返回结果。 - -```sql -CREATE TABLE t1 ( - col1 INT NOT NULL, - col2 DATE NOT NULL, - col3 INT NOT NULL, - col4 INT NOT NULL, - UNIQUE KEY uidx12(col1, col2) GLOBAL, - UNIQUE KEY uidx3(col3), - KEY idx1(col1) GLOBAL -) -PARTITION BY HASH(col3) -PARTITIONS 4; -``` - -在上面示例中,唯一索引 `uidx12` 和非唯一索引 `idx1` 将成为全局索引,但 `uidx3` 仍是常规的唯一索引。 - -请注意,**聚簇索引**不能成为全局索引,如下例所示: - -```sql -CREATE TABLE t2 ( - col1 INT NOT NULL, - col2 DATE NOT NULL, - PRIMARY KEY (col2) CLUSTERED GLOBAL -) PARTITION BY HASH(col1) PARTITIONS 5; -``` - -``` -ERROR 1503 (HY000): A CLUSTERED INDEX must include all columns in the table's partitioning function -``` - -聚簇索引不能成为全局索引,是因为如果聚簇索引是全局索引,则表将不再分区。这是因为聚簇索引的键是分区级别的行数据的键,但全局索引是表级别的,这就造成了冲突。如果需要将主键设置为全局索引,则需要显式设置该主键为非聚簇索引,如 `PRIMARY KEY(col1, col2) NONCLUSTERED GLOBAL`。 - -你可以通过 [`SHOW CREATE TABLE`](/sql-statements/sql-statement-show-create-table.md) 输出中的 `GLOBAL` 索引选项来识别全局索引。 - -```sql -SHOW CREATE TABLE t1\G -``` - -``` - Table: t1 -Create Table: CREATE TABLE `t1` ( - `col1` int NOT NULL, - `col2` date NOT NULL, - `col3` int NOT NULL, - `col4` int NOT NULL, - UNIQUE KEY `uidx12` (`col1`,`col2`) /*T![global_index] GLOBAL */, - 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) -``` - -或查询 [`INFORMATION_SCHEMA.TIDB_INDEXES`](/information-schema/information-schema-tidb-indexes.md) 表并查看输出中的 `IS_GLOBAL` 列来识别全局索引。 - -```sql -SELECT * FROM information_schema.tidb_indexes WHERE table_name='t1'; -``` - -``` -+--------------+------------+------------+----------+--------------+-------------+----------+---------------+------------+----------+------------+-----------+-----------+ -| TABLE_SCHEMA | TABLE_NAME | NON_UNIQUE | KEY_NAME | SEQ_IN_INDEX | COLUMN_NAME | SUB_PART | INDEX_COMMENT | Expression | INDEX_ID | IS_VISIBLE | CLUSTERED | IS_GLOBAL | -+--------------+------------+------------+----------+--------------+-------------+----------+---------------+------------+----------+------------+-----------+-----------+ -| 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, idx1 LOCAL); -``` - -#### 全局索引的限制 - -- 如果索引定义中未显式指定 `GLOBAL` 关键字,TiDB 将默认创建局部索引 (Local Index)。 -- `GLOBAL` 和 `LOCAL` 关键字仅适用于分区表,对非分区表没有影响。即在非分区表中,全局索引和局部索引之间没有区别。 -- 以下 DDL 操作会触发全局索引的更新:`DROP PARTITION`、`TRUNCATE PARTITION` 和 `REORGANIZE PARTITION`。这些 DDL 需等待全局索引更新完成后才会返回结果,耗时会相应增加。尤其是在数据归档场景下,如 `DROP PARTITION` 和 `TRUNCATE PARTITION`,若没有全局索引,通常可以立即完成;但使用全局索引后,耗时会随着所需更新的索引数量的增加而增加。 -- 包含全局索引的表不支持 `EXCHANGE PARTITION`。 -- 默认情况下,分区表的主键为聚簇索引,且必须包含分区键。如果要求主键不包含分区建,可以在建表时显式指定主键为非聚簇的全局索引,例如:`PRIMARY KEY(col1, col2) NONCLUSTERED GLOBAL`。 -- 如果在表达式列上添加了全局索引,或者一个全局索引同时也是前缀索引(如 `UNIQUE KEY idx_id_prefix (id(10)) GLOBAL`),你需要为该全局索引手动收集统计信息。 - ### 关于函数的分区限制 只有以下函数可以用于分区表达式: diff --git a/placement-rules-in-sql.md b/placement-rules-in-sql.md index 30d1df660345..3c062e27f460 100644 --- a/placement-rules-in-sql.md +++ b/placement-rules-in-sql.md @@ -295,7 +295,7 @@ PARTITION BY RANGE( YEAR(purchased) ) ( ); ``` -如果没有为表中的某个分区指定任何放置策略,该分区将尝试继承表上可能存在的策略。如果该表有[全局索引](/partitioned-table.md#全局索引),索引将应用与该表相同的放置策略。在上面示例中: +如果没有为表中的某个分区指定任何放置策略,该分区将尝试继承表上可能存在的策略。如果该表有[全局索引](/global-indexes.md),索引将应用与该表相同的放置策略。在上面示例中: - `p0` 分区将会应用 `storageforhistorydata` 策略 - `p4` 分区将会应用 `storagefornewdata` 策略 diff --git a/releases/release-8.3.0.md b/releases/release-8.3.0.md index 72211d419eff..33b121996743 100644 --- a/releases/release-8.3.0.md +++ b/releases/release-8.3.0.md @@ -119,7 +119,7 @@ TiDB 版本:8.3.0 从 v8.3.0 开始,全局索引作为实验特性正式发布。你可通过关键字 `Global` 为分区表显式创建一个全局索引,从而去除分区表唯一建必须包含分区表达式中用到的所有列的限制,满足灵活的业务需求。同时基于全局索引也提升了非分区列的查询性能。 - 更多信息,请参考[用户文档](/partitioned-table.md#全局索引)。 + 更多信息,请参考[用户文档](/global-indexes.md)。 ### 稳定性 diff --git a/releases/release-8.4.0.md b/releases/release-8.4.0.md index e76889e05b52..e3424616147a 100644 --- a/releases/release-8.4.0.md +++ b/releases/release-8.4.0.md @@ -134,7 +134,7 @@ TiDB 版本:8.4.0 在 v8.4.0 中,全局索引成为正式功能 (GA)。你无需再设置系统变量 [`tidb_enable_global_index`](/system-variables.md#tidb_enable_global_index-从-v760-版本开始引入) 开启全局索引特性,可以直接使用关键字 `GLOBAL` 创建全局索引。从 v8.4.0 开始,该系统变量被废弃,并总是设置为 `ON`。 - 更多信息,请参考[用户文档](/partitioned-table.md#全局索引)。 + 更多信息,请参考[用户文档](/global-indexes.md)。 * 优化缓存表在部分场景下的查询性能 [#43249](https://github.com/pingcap/tidb/issues/43249) @[tiancaiamao](https://github.com/tiancaiamao) @@ -282,7 +282,7 @@ TiDB 版本:8.4.0 |--------|------------------------------|------| | `log_bin` | 删除 | 从 v8.4.0 开始,[TiDB Binlog](https://docs.pingcap.com/zh/tidb/v8.3/tidb-binlog-overview) 被移除。该变量表示是否使用 TiDB Binlog,从 v8.4.0 开始被删除。| | `sql_log_bin` | 删除 | 从 v8.4.0 开始,[TiDB Binlog](https://docs.pingcap.com/zh/tidb/v8.3/tidb-binlog-overview) 被移除。该变量表示是否将更改写入 TiDB Binlog,从 v8.4.0 开始被删除。| -| [`tidb_enable_global_index`](/system-variables.md#tidb_enable_global_index-从-v760-版本开始引入) | 废弃 | 从 v8.4.0 开始,该变量被废弃。其值将固定为默认值 `ON`,即默认启用[全局索引](/partitioned-table.md#全局索引)。你只需在执行 `CREATE TABLE` 或 `ALTER TABLE` 时给对应的列加上关键字 `GLOBAL` 即可创建全局索引。| +| [`tidb_enable_global_index`](/system-variables.md#tidb_enable_global_index-从-v760-版本开始引入) | 废弃 | 从 v8.4.0 开始,该变量被废弃。其值将固定为默认值 `ON`,即默认启用[全局索引](/global-indexes.md)。你只需在执行 `CREATE TABLE` 或 `ALTER TABLE` 时给对应的列加上关键字 `GLOBAL` 即可创建全局索引。| | [`tidb_enable_list_partition`](/system-variables.md#tidb_enable_list_partition-从-v50-版本开始引入) | 废弃 | 从 v8.4.0 开始,该变量被废弃。其值将固定为默认值 `ON`,即默认启用 [List 分区](/partitioned-table.md#list-分区)。| | [`tidb_enable_table_partition`](/system-variables.md#tidb_enable_table_partition) | 废弃 | 从 v8.4.0 开始,该变量被废弃。其值将固定为默认值 `ON`,即默认启用[分区表](/partitioned-table.md)。| | [`tidb_analyze_partition_concurrency`](/system-variables.md#tidb_analyze_partition_concurrency) | 修改 | 取值范围从 `[1, 18446744073709551615]` 修改为 `[1, 128]`。| diff --git a/releases/release-8.5.0.md b/releases/release-8.5.0.md index 877b40441b10..d58fc6d95b84 100644 --- a/releases/release-8.5.0.md +++ b/releases/release-8.5.0.md @@ -51,7 +51,7 @@ TiDB 8.5.0 为长期支持版本 (Long-Term Support Release, LTS)。 实例级执行计划缓存允许同一个 TiDB 实例的所有会话共享执行计划缓存。与现有的会话级执行计划缓存相比,实例级执行计划缓存能够在内存中缓存更多执行计划,减少 SQL 编译时间,从而降低 SQL 整体运行时间,提升 OLTP 的性能和吞吐,同时更好地控制内存使用,提升数据库稳定性。 - 分区表全局索引(从 v8.4.0 开始成为正式功能) + 从 v8.3.0 版本开始引入