全部產品
Search
文件中心

PolarDB:全域索引

更新時間:Jul 06, 2024

全域索引(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 ]

    樣本

    加速非分區鍵查詢

    1. 建立以時間列作為分區鍵的分區表。

      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');
    2. 對該分區表進行非分區鍵的條件查詢。

      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)
    3. 對該分區表建立局部索引,並重新執行查詢。

      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)

    4. 使用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能夠帶來一個數量級的效能提升。