一、概述
·非分区表由于所有索引天然全局覆盖,无需且也无法定义全局索引。
·当用户使用 REMOVE PARTITIONING 将分区表转化为非分区表时,TDSQL Boundless 会将全局索引变成单表的普通索引。
·由于主键需要包含分区键,因此主键作为全局索引无意义且也不支持,但是用户可以使用全局唯一索引以达到类似效果。
二、支持版本
·适用于 TDSQL Boundless V21.6.3.0及以上版本。
·TDSQL Boundless V21.6.4.0 起支持对全局二级索引进行分区(Partitioned GSI),通过在 GLOBAL 关键字后附加 PARTITION BY 子句定义 GSI 自身的分区策略。
三、注意事项
·存在列存节点时,暂不支持创建全局索引。
·全局索引在 DROP PARTITION、TRUNCATE PARTITION 时涉及部分索引失效的情况,用户可选择暂时使索引失效以快速清理分区,也可选择保持索引有效性但减慢分区清理。包含全局索引的分区表执行 DROP PARTITION、TRUNCATE PARTITION 操作请参见 分区 DDL 与 UPDATE GLOBAL INDEXES 子句。
四、创建与使用语法
普通 GSI 通过在索引定义后添加 GLOBAL 关键字来创建。支持 CREATE TABLE 建表时定义、CREATE INDEX 独立创建以及 ALTER TABLE 添加三种方式:
— 方式一:CREATE TABLE 建表时定义CREATE TABLE tbl ( id BIGINT NOT NULL, col1 VARCHAR(32) NOT NULL, col2 INT, PRIMARY KEY (id), INDEX g_idx (col1) GLOBAL, UNIQUE INDEX g_uk (col2) GLOBAL) ENGINE=ROCKSDB PARTITION BY HASH(id) PARTITIONS 4;– 方式二:CREATE INDEX 独立创建CREATE INDEX g_idx2 ON tbl (col1, col2) GLOBAL;– 方式三:ALTER TABLE 添加ALTER TABLE tbl ADD INDEX g_idx3 (col1) GLOBAL;
五、适用场景
·非分区键查询:例如一张按 user_id 哈希分区的订单表,业务还需要频繁按 order_no 查询。若使用 LOCAL 索引,按 order_no 查询会扇出到所有分区;改用 GSI 后可依据实际数据直接定位分区。
CREATE TABLE orders ( user_id BIGINT NOT NULL, order_no VARCHAR(32) NOT NULL, amount DECIMAL(10,2), PRIMARY KEY (user_id, order_no), INDEX g_idx_order_no (order_no) GLOBAL — 全局二级索引) PARTITION BY HASH(user_id) PARTITIONS 4;– 查询时优化器借助 GSI 直接定位分区,无需全分区扫描:SELECT user_id, amount FROM orders WHERE order_no = 'NO20240601001';
·全局唯一约束:例如用户表按 user_id 分区,但要求 email、phone 在全表范围内唯一。LOCAL 唯一索引只能保证分区内唯一,无法跨分区;此时使用全局唯一索引来实现。
CREATE TABLE users ( user_id BIGINT NOT NULL, email VARCHAR(128) NOT NULL, phone VARCHAR(20) NOT NULL, PRIMARY KEY (user_id), UNIQUE INDEX g_uk_email (email) GLOBAL, — 全局唯一:email 全表唯一 UNIQUE INDEX g_uk_phone (phone) GLOBAL — 全局唯一:phone 全表唯一) PARTITION BY HASH(user_id) PARTITIONS 4;INSERT INTO users (user_id, email, phone) VALUES (1, 'test@example.com', '13800000001');INSERT INTO users (user_id, email, phone) VALUES (2, 'test@example.com', '13800000002');– ERROR 1062 (23000): Duplicate entry 'test@example.com' for key 'users.g_uk_email'.INSERT INTO users (user_id, email, phone) VALUES (3, 'test2@example.com', '13800000001');– ERROR 1062 (23000): Duplicate entry '13800000001' for key 'users.g_uk_phone'.
六、分区 DDL 处理
GSI 的索引数据跨所有分区全局存储,因此 DROP PARTITION、TRUNCATE PARTITION 这类改变分区数据的 DDL 会让原有 GSI 与底层数据不再一致。TDSQL Boundless 通过 UPDATE GLOBAL INDEXES 子句来控制此时 GSI 的处理方式。
| 写法 | GSI 处理 | 结果 |
| 不带 UPDATE GLOBAL INDEXES | GSI 被标记为不可用(unusable) | DDL 执行快,但后续走 GSI 的查询会报错,需手动重建 |
| 带 UPDATE GLOBAL INDEXES | 在 DDL 过程中同步重建 GSI | DDL 耗时增加,但 GSI 始终保持可用 |
下面用一张按 order_date 做 RANGE 分区、带 GSI g_idx_order_no 的订单表完整演示两种写法的差异:
— 建表:以 order_date 按年份进行 RANGE 分区,并在 order_no 列上创建全局二级索引CREATE TABLE orders ( order_no VARCHAR(32) NOT NULL, order_date DATE NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(10,2), PRIMARY KEY (order_date, order_no), INDEX g_idx_order_no (order_no) GLOBAL) PARTITION BY RANGE (YEAR(order_date)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026));– 插入测试数据(order_date 为 2023 年,数据落入 p2023 分区)INSERT INTO orders (order_no, order_date, user_id, amount) VALUES ('NO20230601001', '2023-06-01', 1001, 99.50);– 写法一:未携带 UPDATE GLOBAL INDEXES。分区 DDL 执行成功并返回告警,但全局二级索引将被标记为不可用。ALTER TABLE orders TRUNCATE PARTITION p2023;– Query OK, 0 rows affected, 1 warning– 通过 SHOW WARNINGS 查看告警详情:SHOW WARNINGS;– +———+——+————————————————————————————————————————————————————————————+– | Level | Code | Message |– +———+——+————————————————————————————————————————————————————————————+– | Warning | 8510 | SQLEngine system error 'Global secondary index 'g_idx_order_no' has been marked as unusable after TRUNCATE PARTITION without UPDATE GLOBAL INDEXES. Use ALTER TABLE … UPDATE GLOBAL INDEXES to rebuild.' |– +———+——+————————————————————————————————————————————————————————————+– 索引被标记为不可用后,强制使用该全局二级索引进行查询将返回索引不存在错误:SELECT * FROM orders FORCE INDEX(g_idx_order_no) WHERE order_no = 'NO20230601001';– ERROR 1176 (42000): Key 'g_idx_order_no' doesn't exist in table 'orders'.– 恢复方式一:执行携带 UPDATE GLOBAL INDEXES 的分区 DDL,在变更分区的同时重建全局二级索引。ALTER TABLE orders TRUNCATE PARTITION p2024 UPDATE GLOBAL INDEXES;– 恢复方式二:删除后重新创建全局二级索引。ALTER TABLE orders DROP INDEX g_idx_order_no;ALTER TABLE orders ADD INDEX g_idx_order_no (order_no) GLOBAL;– 写法二:携带 UPDATE GLOBAL INDEXES。在执行分区 DDL 的同时同步重建全局二级索引,全程保持索引可用。ALTER TABLE orders TRUNCATE PARTITION p2025 UPDATE GLOBAL INDEXES;
使用建议:
·尽量避免对带 GSI 的表做 DROP PARTITION / TRUNCATE PARTITION:这类分区 DDL 会让全局索引与底层数据失配,要么使 GSI 不可用,要么因同步重建 GSI 而显著拉长 DDL 耗时。若业务存在频繁的分区增删需求,应在建表设计阶段就评估是否真的需要 GSI。
·若确实需要执行此类分区 DDL 且随后仍依赖 GSI 查询,应直接带上 UPDATE GLOBAL INDEXES,一步到位、避免中间出现不可用窗口。
·若追求 DDL 速度、且能接受随后单独重建索引,可省略该子句,后续再执行 ALTER TABLE DROP / ADD INDEX 重建。
·该子句仅作用于 DROP PARTITION 与 TRUNCATE PARTITION 分区操作。
·该子句仅作用于全局二级索引,不影响本地索引。如果表上不存在全局二级索引,该子句不会产生额外作用。
七、概述
自 V21.6.4.0 版本起,TDSQL Boundless 的全局二级索引支持对索引自身进行分区。通过在 GLOBAL 关键字后附加 PARTITION BY 子句,可将一份逻辑 GSI 拆分为多个物理子索引,分散存储至不同数据对象,从而提升并发读写吞吐和缓解访问热点。
八、创建语法
详见 CREATE TABLE。
分区 GSI 通过在 GLOBAL 后增加 PARTITION BY 子句来定义索引自身的分区方式,示例如下:
CREATE TABLE orders ( id BIGINT NOT NULL, order_no VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, merchant_id BIGINT NOT NULL, region_id INT NOT NULL, category_id INT NOT NULL, amount_cents BIGINT NOT NULL, created_day DATE NOT NULL, PRIMARY KEY (id), — 1. HASH INDEX g_hash_user (user_id) GLOBAL PARTITION BY HASH(user_id) PARTITIONS 8, — 2. LINEAR HASH INDEX g_linear_hash_merchant (merchant_id) GLOBAL PARTITION BY LINEAR HASH(merchant_id) PARTITIONS 8, — 3. KEY INDEX g_key_order_region (order_no, region_id) GLOBAL PARTITION BY KEY(order_no, region_id) PARTITIONS 8, — 4. LINEAR KEY INDEX g_linear_key_order_category (order_no, category_id) GLOBAL PARTITION BY LINEAR KEY ALGORITHM = 2 (order_no, category_id) PARTITIONS 8, — 5. RANGE INDEX g_range_amount (amount_cents) GLOBAL PARTITION BY RANGE(amount_cents) (PARTITION p0 VALUES LESS THAN (100000), PARTITION p1 VALUES LESS THAN (500000), PARTITION p2 VALUES LESS THAN MAXVALUE), — 6. RANGE COLUMNS INDEX g_range_columns_region_user (region_id, user_id) GLOBAL PARTITION BY RANGE COLUMNS(region_id, user_id) (PARTITION p0 VALUES LESS THAN (10, 10000), PARTITION p1 VALUES LESS THAN (50, 50000), PARTITION p2 VALUES LESS THAN (MAXVALUE, MAXVALUE))) ENGINE=ROCKSDB PARTITION BY HASH(id) PARTITIONS 4;– 1. HASHCREATE INDEX g_hash_category ON orders(category_id) GLOBAL PARTITION BY HASH(category_id) PARTITIONS 16;– 2. LINEAR HASHCREATE INDEX g_linear_hash_region ON orders(region_id) GLOBAL PARTITION BY LINEAR HASH(region_id) PARTITIONS 16;– 3. KEYCREATE INDEX g_key_order_merchant ON orders(order_no, merchant_id) GLOBAL PARTITION BY KEY(order_no, merchant_id) PARTITIONS 16;– 4. LINEAR KEYCREATE INDEX g_linear_key_order_user ON orders(order_no, user_id) GLOBAL PARTITION BY LINEAR KEY ALGORITHM = 1 (order_no, user_id) PARTITIONS 16;– 5. RANGECREATE INDEX g_range_user ON orders(user_id) GLOBAL PARTITION BY RANGE(user_id) (PARTITION p0 VALUES LESS THAN (10000), PARTITION p1 VALUES LESS THAN (50000), PARTITION p2 VALUES LESS THAN MAXVALUE);– 6. RANGE COLUMNSCREATE INDEX g_range_columns_category_amount ON orders(category_id, amount_cents) GLOBAL PARTITION BY RANGE COLUMNS(category_id, amount_cents) (PARTITION p0 VALUES LESS THAN (10, 100000), PARTITION p1 VALUES LESS THAN (50, 500000), PARTITION p2 VALUES LESS THAN (MAXVALUE, MAXVALUE));
九、分区裁剪
全局分区索引按照指定规则将索引数据分散到多个分区,避免单个 GSI 承担全部读写压力。查询携带索引分区键条件时,优化器可以裁剪无关的 GSI 分区:例如等值查询仅访问 g_hash_user 的 p1,范围查询仅访问 g_range_amount 的 p0、p1,分散热点和访问压力。命中索引后,再根据索引记录中的基表分区信息直接定位并回表。
explain select * from orders where user_id = 1;+—-+————-+——–+————-+——+—————+————-+———+——-+——+———-+———————————————————+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——–+————-+——+—————+————-+———+——-+——+———-+———————————————————+| 1 | SIMPLE | orders | p0,p1,p2,p3 | ref | g_hash_user | g_hash_user | 8 | const | 1 | 100.00 | Using global index; Using GSI partition g_hash_user: p1 |+—-+————-+——–+————-+——+—————+————-+———+——-+——+———-+———————————————————+explain select * from orders where amount_cents > 100 and amount_cents < 500000;+—-+————-+——–+————-+——-+—————-+—————-+———+——+——+———-+—————————————————————+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——–+————-+——-+—————-+—————-+———+——+——+———-+—————————————————————+| 1 | SIMPLE | orders | p0,p1,p2,p3 | range | g_range_amount | g_range_amount | 8 | NULL | 2 | 100.00 | Using global index; Using GSI partition g_range_amount: p0,p1 |+—-+————-+——–+————-+——-+—————-+—————-+———+——+——+———-+—————————————————————+
十、系统视图
INFORMATION_SCHEMA.GLOBAL_INDEX_PARTITIONS 系统视图提供了分区 GSI 的子索引分区信息。详见 INFORMATION_SCHEMA.GLOBAL_INDEX_PARTITIONS。