全域索引(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能夠帶來一個數量級的效能提升。