1. 程式人生 > >PostgreSQL 11 新特性之覆蓋索引(Covering Index)

PostgreSQL 11 新特性之覆蓋索引(Covering Index)

文章目錄

通常來說,索引可以用於提高查詢的速度。通過索引,可以快速訪問表中的指定資料,避免了表上的掃描。

有時候,索引不僅僅能夠用於定位表中的資料。某些查詢可能只需要訪問索引的資料,就能夠獲取所需要的結果,而不需要再次訪問表中的資料。這種訪問資料的方法叫做 Index-Only 掃描。

要想通過索引直接返回查詢的資料,建立的索引需要包含 SELECT 列表中的所有欄位:

CREATE TABLE t1 (a int, b int, c int);

INSERT INTO t1
     SELECT val, val + 1, val * 2
     FROM
generate_series(1, 100000) as val; CREATE UNIQUE INDEX idx_t1_ab ON t1(a, b); ANALYZE;

以下查詢使用欄位 a 作為條件,並返回 a 和 b:

EXPLAIN SELECT a, b FROM t1 WHERE a BETWEEN 100 AND 200;
QUERY PLAN                                                                |
--------------------------------------------------------------------------|
Index Only Scan using idx_t1_ab on t1 (cost=0.29..166.00 rows=98 width=8)| Index Cond: ((a >= 100) AND (a <= 200)) |

通過查詢計劃可以看出,以上查詢使用了 Index-Only Scan,直接通過索引掃描就可以返回查詢的結果。

許多資料庫產品對於這種包含了查詢結果的索引稱為覆蓋索引(covering index),不過更準確的說法應該是 Index-Only 掃描。它只是執行計劃訪問資料的一種方式,而不是一種新的索引。

我們修改一下查詢,仍然以欄位 a 作為查詢條件,但是要求返回 a 和 c:

EXPLAIN SELECT a, c FROM t1 WHERE a BETWEEN 100 AND 200;
QUERY PLAN                                                           |
---------------------------------------------------------------------|
Index Scan using idx_t1_ab on t1  (cost=0.29..166.00 rows=98 width=8)|
  Index Cond: ((a >= 100) AND (a <= 200))                            |

由於欄位 c 不在索引 idx_t1_ab 中,查詢雖然使用了索引掃描,但是仍然需要通過索引二次查詢表中的資料。如果想要使用 Index-Only 掃描,需要再基於欄位 a, c 建立一個新的索引。

如果查詢需要返回 a, b, c,還需要第 3 個索引。如果使用 a, b, c 上的索引替代 idx_t1_ab,又無法保證 a, b 上的唯一性。

為此,PostreSQL 11 提供了一個新的索引子句,即 INCLUDE 子句:

DROP INDEX idx_t1_ab;

CREATE UNIQUE INDEX idx_t1_ab ON t1 USING btree (a, b) INCLUDE (c);
ANALYZE;

Db2 和 SQL Server 也有類似 INCLUDE 子句。

以上唯一索引仍然基於欄位 a, b 建立,同時使用 INCLUDE 子句在索引的葉子節點儲存欄位 c 的值。因此,以下查詢也能夠使用 Index-Only 掃描:

EXPLAIN SELECT a, c FROM t1 WHERE a BETWEEN 100 AND 200;
QUERY PLAN                                                                 |
---------------------------------------------------------------------------|
Index Only Scan using idx_t1_ab on t1  (cost=0.42..176.77 rows=105 width=8)|
  Index Cond: ((a >= 100) AND (a <= 200))                                  |

EXPLAIN SELECT a, b, c FROM t1 WHERE a=100 and b BETWEEN 100 AND 200;
QUERY PLAN                                                              |
------------------------------------------------------------------------|
Index Only Scan using idx_t1_ab on t1  (cost=0.42..8.44 rows=1 width=12)|
  Index Cond: ((a = 100) AND (b >= 100) AND (b <= 200))                 |

接下來看一看官方文件中的介紹:

CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]
    ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
    [ INCLUDE ( column_name [, ...] ) ]
    [ WITH ( storage_parameter = value [, ... ] ) ]
    [ TABLESPACE tablespace_name ]
    [ WHERE predicate ]

INCLUDE 子句可以為索引增加一些非鍵值的欄位。這些非鍵欄位不能用於索引的掃描條件,並且也不會參與索引的唯一性約束和排除約束。 不過,Index-Only 掃描方式可以返回這些非鍵欄位的值,而不需要訪問索引所在的表,因為可以直接從索引節點中直接返回它們的值。因此,在索引中包含非索引鍵的欄位擴充套件了 Index-Only 掃描的使用場景。

使用上面的示例表:

EXPLAIN SELECT a, b, b FROM t1 WHERE a=100 and b =100 and c=100;
QUERY PLAN                                                              |
------------------------------------------------------------------------|
Index Only Scan using idx_t1_ab on t1  (cost=0.42..8.44 rows=1 width=12)|
  Index Cond: ((a = 100) AND (b = 100))                                 |
  Filter: (c = 100)                                                     |

查詢計劃中的索引掃描條件為 ‘((a = 100) AND (b = 100))’,而 ‘(c = 100)’ 只是作為過濾條件。

另外,索引 idx_t1_ab 的唯一性只能確保欄位 a, b 的組合唯一,不包括欄位 c 的值。

為索引新增非鍵欄位時需要謹慎考慮,特別是寬列。如果一個索引記錄超過了索引型別允許的最大值,資料操作將會失敗。此外,非鍵欄位重複儲存了表中的資料,並且增加了索引的大小,可能會導致查詢變慢。

INCLUDE 子句中的列不需要相應的操作符類;該子句可以包含沒有為特定訪問方式定義操作符的資料型別。因為這些欄位僅僅用於返回資料,而不參與索引的掃描。

表示式(函式)不能作為 INCLUDE 欄位,因為 Index-Only 掃描不支援表示式。

CREATE UNIQUE INDEX idx_t1_exp ON t1(a, b) INCLUDE ((c+1));
SQL Error [0A000]: ERROR: expressions are not supported in included columns

目前只有 B-tree 索引支援 INCLUDE 子句。在 B-tree 索引中 ,INCLUDE 子句中的欄位值只儲存在葉子節點中,而不會包含在上層的導航節點中。

覆蓋索引還是優化連線查詢的一個非常好的方法,參考:Covering Indexes for Query Optimization

參考文章:Postgres 11 highlight - Covering Indexes

人生本來短暫,你又何必匆匆!點個贊再走吧!