概述
全局二级索引(Global Secondary Index,GSI)是 TDSQL Boundless 分区表提供的一种跨分区索引类型。与默认的 LOCAL 索引(每个分区独立存储索引数据)不同,GSI 将索引数据跨所有分区全局有序存储,使优化器可以直接通过 GSI 定位数据所在分区,无需扫描所有分区。
非分区表由于所有索引天然全局覆盖,无需且也无法定义全局索引。
当用户使用
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
创建与使用语法
普通 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 分区操作。该子句仅作用于全局二级索引,不影响本地索引。如果表上不存在全局二级索引,该子句不会产生额外作用。
分区 GSI
概述
自 V21.6.4.0 版本起,TDSQL Boundless 的全局二级索引支持对索引自身进行分区。通过在
GLOBAL 关键字后附加 PARTITION BY 子句,可将一份逻辑 GSI 拆分为多个物理子索引,分散存储至不同数据对象,从而提升并发读写吞吐和缓解访问热点。创建语法
分区 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. HASHINDEX g_hash_user (user_id) GLOBAL PARTITION BY HASH(user_id) PARTITIONS 8,-- 2. LINEAR HASHINDEX g_linear_hash_merchant (merchant_id) GLOBAL PARTITION BY LINEAR HASH(merchant_id) PARTITIONS 8,-- 3. KEYINDEX g_key_order_region (order_no, region_id) GLOBAL PARTITION BY KEY(order_no, region_id) PARTITIONS 8,-- 4. LINEAR KEYINDEX g_linear_key_order_category (order_no, category_id) GLOBAL PARTITION BY LINEAR KEY ALGORITHM = 2 (order_no, category_id) PARTITIONS 8,-- 5. RANGEINDEX 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 COLUMNSINDEX 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。