Global indexes can be created on fields other than the partition key of a partitioned table. They support unique constraints. This topic gives an overview of global indexes on partitioned tables.
Background information
A local index on a partitioned table refers to an index partition mapped to each table partition. The partitioning of local indexes is consistent with that of tables. Therefore, a local index must include the partition key to support unique constraints.
A global index is a single index defined for the entire partitioned table and spans many table partitions. When a partitioned table without a global index is queried, the system has to retrieve data from the table's physical partitions, which are distributed across the storage. With a global index, the system can access data from multiple partitions in a collective manner, instead of accessing partitions separately.
A global index is a B-Tree index created on the partitioned table rather than on separate child partitions. It can be created on non-partition key columns and it supports unique constraints.
Global partitioned indexes
Global partitioned indexes are often created on partitioned tables that contain large data volumes, and thus are more complicated to build. The partitioning of global partitioned indexes does not necessarily align with the partitioning of tables. In addition to efficiently locating relevant records across partitions, global partitioned indexes offer extra performance boost for queries targeting the indexed partition key.
Create a global index
Syntax
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/global_partitioned_index ]
[ TABLESPACE tablespace_name ]
[ WHERE predicate ]
global_partitioned_index:
GLOBAL PARTITION BY {RANGE (column_list) (index_partitioning_clause) |
HASH (column_list) (hash_partitions_by_quantity)}
Create a global index on a partitioned table.
CREATE unique index m_city_id_idx_global ON measurement(city_id) global;
Create a partitioned global index on a partitioned table.
CREATE UNIQUE INDEX m_peaktemp_idx_global ON measurement(peaktemp) global
PARTITION BY range (peaktemp)
(
PARTITION m_peaktemp_idx_global_p1 values less than(1000),
PARTITION m_peaktemp_idx_global_p2 values less than(5000),
PARTITION m_peaktemp_idx_global_p3 values less than(MAXVALUE)
);
Notes
The system creates a global index when
GLOBAL
is specified.If you do not specify the
GLOBAL
keyword, a local index is created by default.You can specify CONCURRENTLY in a CREATE INDEX statement that creates a global index.
You cannot create global indexes on non-partitioned tables or the child tables of partitioned tables.
You cannot create global indexes based on expressions.
Global indexes can be range- or hash-partitioned. The syntax is similar to that for partitioning a data table.
Global indexes cannot be list-partitioned.
Global indexes can be partitioned on the table partition key.
Global partitioned indexes can enforce unique constraints globally.
The partitioning of a global index must be based on a left prefix of the index columns.
Range-partitioned global indexes support ordered scanning.
Hash-partitioned global indexes do not support sequential scans.
Global partitioned indexes can be used to implement partition pruning based on optimizers and executors.
Global index scan
Similar to a B-Tree index, when the system performs a query based on the index key, a global index makes for better performance due to index scans.
Global indexes support the following types of index scans:
Global index scan
Global partitioned index scan
Global index-only scan
Global partitioned index-only scan
Global index bitmap scan
Global partitioned index bitmap scan
Global index parallel scan
Global only index parallel scan
Examples
Global index scan
explain select * from measurement where city_id = 5; QUERY PLAN ------------------------------------------------------------------------------------------------ Global Index Scan using m_city_id_idx_global on measurement (cost=0.12..8.14 rows=1 width=20) Index Cond: (city_id = 5) (2 rows)
Global partitioned index scan
explain select * from measurement where peaktemp = 5; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Global Partitioned Index Scan(1/3) using m_peaktemp_idx_global on measurement (cost=0.50..8.52 rows=1 width=20) Index Cond: (peaktemp = 5) (2 rows)
Global index-only scan
explain select city_id from measurement where city_id = 5; QUERY PLAN ---------------------------------------------------------------------------------------------------- Global Index Only Scan using m_city_id_idx_global on measurement (cost=0.12..8.14 rows=1 width=4) Index Cond: (city_id = 5) (2 rows)
Global partitioned index-only scan
explain select peaktemp from measurement where peaktemp = 5; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Global Partitioned Index Only Scan(1/3) using m_peaktemp_idx_global on measurement (cost=0.12..8.14 rows=1 width=4) Index Cond: (peaktemp = 5) (2 rows)
Global index bitmap scan
explain select * from measurement where city_id > 5 and city_id < 10000; QUERY PLAN ------------------------------------------------------------------------------------------------ Append (cost=1.03..14.47 rows=32 width=20) -> Bitmap Heap Scan on measurement_y2023q1 (cost=1.03..3.58 rows=8 width=20) Recheck Cond: ((city_id > 5) AND (city_id < 10000)) Filter: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Global Index Scan on m_city_id_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Heap Scan on measurement_y2023q2 (cost=1.03..3.58 rows=8 width=20) Recheck Cond: ((city_id > 5) AND (city_id < 10000)) Filter: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Global Index Scan on m_city_id_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Heap Scan on measurement_y2023q3 (cost=1.03..3.58 rows=8 width=20) Recheck Cond: ((city_id > 5) AND (city_id < 10000)) Filter: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Global Index Scan on m_city_id_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Heap Scan on measurement_y2023q4 (cost=1.03..3.58 rows=8 width=20) Recheck Cond: ((city_id > 5) AND (city_id < 10000)) Filter: ((city_id > 5) AND (city_id < 10000)) -> Bitmap Global Index Scan on m_city_id_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((city_id > 5) AND (city_id < 10000)) (21 rows)
Global partitioned index bitmap scan
explain select peaktemp from measurement where peaktemp > 5 and peaktemp< 1000; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Append (cost=1.03..14.47 rows=32 width=4) -> Bitmap Heap Scan on measurement_y2023q1 (cost=1.03..3.58 rows=8 width=4) Recheck Cond: ((peaktemp > 5) AND (peaktemp < 1000)) Filter: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Global Partitioned Index Scan(1/3) on m_peaktemp_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Heap Scan on measurement_y2023q2 (cost=1.03..3.58 rows=8 width=4) Recheck Cond: ((peaktemp > 5) AND (peaktemp < 1000)) Filter: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Global Partitioned Index Scan(1/3) on m_peaktemp_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Heap Scan on measurement_y2023q3 (cost=1.03..3.58 rows=8 width=4) Recheck Cond: ((peaktemp > 5) AND (peaktemp < 1000)) Filter: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Global Partitioned Index Scan(1/3) on m_peaktemp_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Heap Scan on measurement_y2023q4 (cost=1.03..3.58 rows=8 width=4) Recheck Cond: ((peaktemp > 5) AND (peaktemp < 1000)) Filter: ((peaktemp > 5) AND (peaktemp < 1000)) -> Bitmap Global Partitioned Index Scan(1/3) on m_peaktemp_idx_global (cost=0.00..4.13 rows=1 width=0) Index Cond: ((peaktemp > 5) AND (peaktemp < 1000)) (21 rows)
Global index column as the primary key or foreign key
In PolarDB for PostgreSQL (Compatible with Oracle), any column of a partitioned table can be the primary key or a foreign key, thanks to global indexes. This is because the prerequisite for defining the primary key or a foreign key is the existence of a unique index on the desired column. A global index can be created unique on any column, while a local unique index must include the partition key. This means without global indexes, the definition of the primary key or a foreign key must always contain the partition key. With global indexes, it becomes more flexible to create the primary key or a foreign key.
You can specify the primary key when you are creating a partitioned table, and the system will decide whether to create local indexes or a global index based on the specified primary key. If the specified primary key contains the partition key, the system creates local unique indexes. Otherwise, a global unique index is created.
Examples
Create a partitioned table with the primary key specified.
-- local index CREATE TABLE pk_rel(a int primary key, b int, c int, d int) PARTITION BY RANGE (a); postgres=# \d pk_rel Table "public.pk_rel" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- a | integer | | not null | b | integer | | | c | integer | | | d | integer | | | Partition key: RANGE (a) Indexes: "pk_rel_pkey" PRIMARY KEY, btree (a) Number of partitions: 0 -- global index CREATE TABLE pk_rel(a int, b int primary key, c int, d int) PARTITION BY RANGE (a); postgres=# \d pk_rel Table "public.pk_rel" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- a | integer | | | b | integer | | not null | c | integer | | | d | integer | | | Partition key: RANGE (a) Indexes: "pk_rel_pkey" PRIMARY KEY, btree (b) GLOBAL Number of partitions: 0
NoteYou can also use this statement to explicitly specify an index as the primary key:
ALTER TABLE xxx ADD PRIMARY KEY USING INDEX xxx
.PolarDB for PostgreSQL (Compatible with Oracle) allows any column of a partitioned table to be a foreign key, provided that a primary key or a unique index has been created on the column. If the column is the partition key, local indexes are created. If it isn't, a global index is created.
Create a partitioned table with a foreign key specified
CREATE TABLE fk_rel(a int, b int REFERENCES pk_rel(b), c int, d int) PARTITION BY RANGE (a); postgres=# \d fk_rel Table "public.fk_rel" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- a | integer | | | b | integer | | | c | integer | | | d | integer | | | Partition key: RANGE (a) Foreign-key constraints: "fk_rel_b_fkey" FOREIGN KEY (b) REFERENCES pk_rel(b) Number of partitions: 0
Performance tests
pgbench is used to generate 80,000
rows of data, and create a partitioned table and a non-partitioned table.
Transactions per second (TPS) cannot be used as an absolute standard. It varies according to different physical environments. The data here is used to compare the performance of global indexes and local indexes.
Point query performance on non-partition keys
Item | TPS | |||||
Prepared statement | Not used | Used | ||||
Concurrency | 1 | 32 | 64 | 1 | 32 | 64 |
Non-partitioned table | 27,732 | 494,433 | 430,848 | 53,935 | 985,880 | 886,882 |
Partitioned table with local indexes | 367 | 4,155 | 3,688 | 856 | 8,742 | 6,790 |
Partitioned table with a global index | 19,006 | 308,128 | 262,941 | 45,090 | 820,924 | 731,557 |
TPC-B performance on non-partition keys
Both point queries and DML are included.
Item | TPS | |||||
Prepared statement | Not used | Used | ||||
Concurrency | 1 | 32 | 64 | 1 | 32 | 64 |
Non-partitioned table | 1,115 | 51,025 | 60,409 | 4,822 | 90,312 | 100,802 |
Partitioned table with local indexes | 271 | 2,903 | 2,524 | 550 | 5,276 | 4,237 |
Partitioned table with a global index | 3,453 | 36,320 | 39,941 | 4,334 | 69,040 | 75,232 |
Conclusion
Global indexes can significantly improve performance for point queries and DML statements.