全局索引(GLOBAL INDEX)是分区表上的一种索引技术,可以创建在分区表的非分区键上,也支持提供唯一约束。
前提条件
支持的PolarDB PostgreSQL版的版本如下:
PostgreSQL 14(内核小版本14.6.4.0及以上)
您可通过如下语句查看PolarDB PostgreSQL版的内核小版本的版本号:
select version();
背景信息
随着业务数据规模的增长,数据分区作为重要的企业级数据库特性,按维度拆分数据成为减小数据规模的重要手段。分区表可以将一个表按照维度(分区键)拆分为若干个独立的子表,通过对子表的分别管理,达到提高可管理性、整体性能和负载均衡的效果。
PolarDB PostgreSQL版的许多用户大量的使用分区表来管理自己的数据,其中较为典型的用法是以时间为维度对分区进行管理:
分区表使用时间作为分区键。
定期(每周或每月)创建新的子分区,新数据进入新的子分区中。
定期对旧的子分区进行归档,减轻分区表的运维成本。
在以上场景中,时间通常被用作分区键,而一般不会被用作主键或unique ID。因此带来了以下两个问题:
对非分区键的查询,由于无法确定数据位于哪个分区中,将不得不扫描所有的子分区。
对非分区键的数据修改,无法保证在整个分区表中的唯一性。
因此,PolarDB PostgreSQL版提供了全局索引功能。全局索引(Global Index)是一种在分区表上创建的索引。不同于默认在每个子分区上创建的局部索引(Local Index,一个索引对应一个子分区),全局索引通过一个索引来索引整个分区表的数据(一个索引对应多个子分区),从而可以提供非分区键上的全局唯一约束,也可以大幅提升非分区键的查询性能。
使用限制
在带有全局索引的分区表上,依然支持
ATTACH
/DETACH
子分区。在创建索引的语法中,通过指定
GLOBAL
关键字来创建全局索引。如果不指定,则默认创建局部索引。全局索引支持
CONCURRENTLY
并发创建。非分区表、带有子分区的子表上不支持创建全局索引。
全局索引不支持表达式索引。
无法在分区表的分区列上创建全局索引。
语法
创建全局索引。
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]
( { column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ INCLUDE ( column_name [, ...] ) ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ GLOBAL/LOCAL ]
[ TABLESPACE tablespace_name ]
[ WHERE predicate ]
示例
加速非分区键查询
创建以时间列作为分区键的分区表。
CREATE TABLE partition_range ( id INT, a INT, b INT, created_date TIMESTAMP WITHOUT TIME ZONE ) PARTITION BY RANGE (created_date); CREATE TABLE partition_range_part01 PARTITION OF partition_range FOR VALUES FROM (MINVALUE) TO ('2020-01-01 00:00:00'); CREATE TABLE partition_range_part02 PARTITION OF partition_range FOR VALUES FROM ('2020-01-01 00:00:00') TO ('2020-02-01 00:00:00'); CREATE TABLE partition_range_part03 PARTITION OF partition_range FOR VALUES FROM ('2020-02-01 00:00:00') TO ('2020-03-01 00:00:00');
对该分区表进行非分区键的条件查询。
EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;
结果显示如下:将会对所有的子分区进行扫描,无法利用到分区表的分区裁剪功能。
QUERY PLAN ------------------------------------------------------------ Append -> Seq Scan on partition_range_part01 partition_range_1 Filter: (id = 1) -> Seq Scan on partition_range_part02 partition_range_2 Filter: (id = 1) -> Seq Scan on partition_range_part03 partition_range_3 Filter: (id = 1) (7 rows)
对该分区表创建局部索引,并重新执行查询。
CREATE INDEX partition_range_idx_local ON partition_range(id); EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;
结果显示如下:由于局部索引建立在每一个子分区上,所以依然需要扫描所有子分区的局部索引。
QUERY PLAN -------------------------------------------------------------------------------------------------- Append -> Index Scan using partition_range_part01_id_idx on partition_range_part01 partition_range_1 Index Cond: (id = 1) -> Index Scan using partition_range_part02_id_idx on partition_range_part02 partition_range_2 Index Cond: (id = 1) -> Index Scan using partition_range_part03_id_idx on partition_range_part03 partition_range_3 Index Cond: (id = 1) (7 rows)
使用
GLOBAL
关键字对该分区表创建全局索引,并重新执行查询。CREATE INDEX partition_range_idx_global ON partition_range(id) GLOBAL; EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;
结果显示如下:数据库将使用全局索引直接找到数据所在的子分区。
QUERY PLAN ----------------------------------------------------------------------- Global Index Scan using partition_range_idx_global on partition_range Index Cond: (id = 1) (2 rows)
非分区键的唯一约束
依然使用上述示例中的分区表,其分区键为时间created_date
,但其真正的唯一约束列为id
。
CREATE UNIQUE INDEX partition_range_id_unique_idx ON partition_range(id);
ERROR: unique constraint on partitioned table must include all partitioning columns
DETAIL: UNIQUE constraint on table "partition_range" lacks column "created_date" which is part of the partition key.
对非分区键创建局部索引的唯一约束将会报错,要求分区键必须被包含在索引中。
而对全局索引添加唯一约束则不会有这个限制,如下所示。
CREATE UNIQUE INDEX partition_range_id_unique_idx ON partition_range(id) GLOBAL;
性能测试
使用pgbench工具产生scale为80000
的数据,分别创建分区表与非分区表。
非分区键上的点查性能
类别 | TPS | |||||
Prepared Statement | 不使用 | 使用 | ||||
并发数 | 1 | 32 | 64 | 1 | 32 | 64 |
普通表 | 27,732 | 494,433 | 430,848 | 53,935 | 985,880 | 886,882 |
分区表+局部索引 | 367 | 4,155 | 3,688 | 856 | 8,742 | 6,790 |
分区表+全局索引 | 19,006 | 308,128 | 262,941 | 45,090 | 820,924 | 731,557 |
非分区键上的TPC-B性能
包含了点查和DML。
类别 | TPS | |||||
Prepared Statement | 不使用 | 使用 | ||||
并发数 | 1 | 32 | 64 | 1 | 32 | 64 |
普通表 | 1,115 | 51,025 | 60,409 | 4,822 | 90,312 | 100,802 |
分区表+局部索引 | 271 | 2,903 | 2,524 | 550 | 5,276 | 4,237 |
分区表+全局索引 | 暂不支持 | 4,334 | 69,040 | 75,232 |
结论
全局索引对分区表的点查和DML能够带来一个数量级的性能提升。