Global indexes

更新时间:
复制 MD 格式

When you partition a table by time, querying or enforcing uniqueness on a non-time column becomes a problem: without a way to span all subpartitions with a single index, every query must scan every subpartition, and cross-partition unique constraints are impossible. Global indexes solve both problems by letting a single index cover all subpartitions of a partitioned table.

Supported versions

PolarDB for PostgreSQL clusters running PostgreSQL 14 with minor engine version 2.0.14.9.15.0 or later.

Check your minor engine version in the console or by running:

SHOW polardb_version;

If the version does not meet the requirement, upgrade the minor engine version.

How global indexes work

A partitioned table splits data into independent subpartitions based on a partition key. A local index creates one index per subpartition. A global index creates a single index that spans all subpartitions.

A common partitioning pattern is time-based: use a timestamp column as the partition key, create new subpartitions weekly or monthly, and archive old ones to reduce operations and maintenance (O&M) costs. In this pattern, the partition key is a time column — not the primary identifier. This causes two problems:

  • No partition pruning on non-partition key queries: Querying by id forces the database to scan all subpartitions, because it cannot determine which one contains the data.

  • No cross-partition uniqueness: A local UNIQUE index cannot enforce uniqueness across the entire partitioned table.

Global index vs. local index

DimensionGlobal indexLocal index
Index scopeCovers the entire partitioned tableOne index per subpartition
Query on non-partition keySingle index scanScans all subpartitions
UNIQUE constraint on non-partition keySupportedNot supported
ATTACH/DETACH subpartitionsSupportedSupported
Expression indexesNot supportedSupported
Index on partition key columnsNot supportedSupported
ManageabilityMore complex (spans all subpartitions)Simpler (scoped to each subpartition)

Limitations

  • Specify the GLOBAL keyword in CREATE INDEX to create a global index. Without it, a local index is created by default.

  • Global indexes support concurrent creation using the CONCURRENTLY keyword.

  • Global indexes are only supported on partitioned tables — not on non-partitioned tables or on child tables that contain subpartitions.

  • Global indexes do not support expression indexes.

  • Global indexes cannot be created on the partition key columns of a partitioned table.

  • You can still ATTACH or DETACH subpartitions from a partitioned table that has 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 ]
    [ TABLESPACE tablespace_name ]
    [ WHERE predicate ]

Examples

Accelerate non-partition key queries

Step 1: Create a partitioned table.

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');

Step 2: Query without an index.

EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;

All subpartitions are scanned because partition pruning cannot apply to a non-partition key:

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)

Step 3: Add a local index and query again.

CREATE INDEX partition_range_idx_local ON partition_range(id);

EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;

All subpartition indexes are still scanned, because local indexes are created on each subpartition individually:

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)

Step 4: Add a global index and query again.

CREATE INDEX partition_range_idx_global ON partition_range(id) GLOBAL;

EXPLAIN (COSTS OFF) SELECT * FROM partition_range WHERE id = 1;

The database uses the global index to locate the target subpartition directly — 7 plan nodes reduced to 2:

QUERY PLAN
-----------------------------------------------------------------------
 Global Index Scan using partition_range_idx_global on partition_range
   Index Cond: (id = 1)
(2 rows)

Enforce a unique constraint on a non-partition key

The partition key for partition_range is created_date, but the column that requires a unique constraint is id.

In native PostgreSQL, creating a UNIQUE index on a non-partition key column fails:

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.

PolarDB for PostgreSQL removes this restriction. By default, a UNIQUE constraint on a non-partition key is automatically enforced as a global constraint backed by a global index. This behavior is controlled by the polar_pk_in_non_partition_column_mode parameter:

ValueBehavior
global_index (default)UNIQUE constraint on a non-partition key is converted to a global constraint
noneSame behavior as native PostgreSQL — UNIQUE on a non-partition key fails with an error

To create the unique global index explicitly:

CREATE UNIQUE INDEX partition_range_id_unique_idx ON partition_range(id) GLOBAL;

Performance benchmark

The following results are from pgbench with a scale factor of 80,000, comparing a standard table, a partitioned table with local indexes, and a partitioned table with global indexes. All examples use single-row point queries on a non-partition key column.

Point query performance (TPS)

ConfigurationConcurrency 1Concurrency 32Concurrency 64Concurrency 1 (PS)Concurrency 32 (PS)Concurrency 64 (PS)
Standard table27,732494,433430,84853,935985,880886,882
Partitioned table + local index3674,1553,6888568,7426,790
Partitioned table + global index19,006308,128262,94145,090820,924731,557

PS = Prepared Statement

TPC-B performance (TPS)

TPC-B includes both point queries and Data Manipulation Language (DML) statements.

ConfigurationConcurrency 1Concurrency 32Concurrency 64Concurrency 1 (PS)Concurrency 32 (PS)Concurrency 64 (PS)
Standard table1,11551,02560,4094,82290,312100,802
Partitioned table + local index2712,9032,5245505,2764,237
Partitioned table + global indexNot supported4,33469,04075,232

Summary

Global indexes improve point query and DML performance on partitioned tables by an order of magnitude compared to local indexes.