PostgreSQL 表达式索引

本文介绍在 PostgreSQL 中如何创建表达式索引。

除了在列上创建索引, PostgreSQL 允许您在基于表中的一列或者多列的表达上是创建索引,这称为表达式索引。

请注意,表达式上的索引维护起来非常昂贵,因为 PostgreSQL 必须在插入更新每一行时评估每一行的表达式,并将结果用于索引。因此,只有当检索速度比插入和更新速度更重要时,您才应该在表达式上使用索引。

PostgreSQL 表达式索引语法

要创建表达式索引,请遵循如下语法:

CREATE INDEX index_name
ON table_name ( (expression) );

解释:

  • 创建表达式索引和创建普通索引的语法基本相同,只是由表达式代替了列名。
  • 表达式一般通过圆括号包含起来。如果表达式只是一个函数调用,则可以省率圆括号。
  • index_name 为索引名称。 您可以省率索引名称,并且 PostgreSQL 会自动生成一个。

一旦定义了索引表达式,当定义索引的表达式出现在 WHERE 子句或 ORDER BY SQL 语句的子句中时,PostgreSQL 将考虑使用该索引。

PostgreSQL 表达式索引示例

下面通过 PostgreSQL Sakila 示例数据库中的 customer 表来演示 PostgreSQL 表达式索引。

下面是 customer 表的部分定义:

                                             Table "public.customer"
   Column    |            Type             | Collation | Nullable |                    Default
-------------+-----------------------------+-----------+----------+-----------------------------------------------
 customer_id | integer                     |           | not null | nextval('customer_customer_id_seq'::regclass)
 store_id    | smallint                    |           | not null |
 first_name  | character varying(45)       |           | not null |
 last_name   | character varying(45)       |           | not null |
 email       | character varying(50)       |           |          |
 address_id  | smallint                    |           | not null |
 activebool  | boolean                     |           | not null | true
 create_date | date                        |           | not null | 'now'::text::date
 last_update | timestamp without time zone |           |          | now()
 active      | integer                     |           |          |
Indexes:
    "customer_pkey" PRIMARY KEY, btree (customer_id)
    "idx_fk_address_id" btree (address_id)
    "idx_fk_store_id" btree (store_id)
    "idx_last_name" btree (last_name)

...

customer 表中有一个为 last_name 列定义的 B 树索引 idx_last_name。以下 EXPLAIN 语句展示查询查找姓氏为 Adam 的客户时,PostgreSQL 使用 idx_last_name 索引:

EXPLAIN
SELECT * FROM customer
WHERE last_name = 'Adam';
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Index Scan using idx_last_name on customer  (cost=0.28..8.29 rows=1 width=70)
   Index Cond: ((last_name)::text = 'Adam'::text)
(2 rows)

但是,当查找姓氏为 adam 小写的客户时,PostgreSQL 无法利用索引进行查找:

EXPLAIN
SELECT * FROM customer
WHERE LOWER(last_name) = 'adam';
                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on customer  (cost=0.00..16.98 rows=3 width=70)
   Filter: (lower((last_name)::text) = 'adam'::text)
(2 rows)

为了提高 LOWER() 函数的查询效率,你可以定义一个这样的索引表达式:

CREATE INDEX ON customer(LOWER(last_name));

现在,再次查看上面语句执行计划,

EXPLAIN
SELECT * FROM customer
WHERE LOWER(last_name) = 'adam';
                                   QUERY PLAN
---------------------------------------------------------------------------------
 Bitmap Heap Scan on customer  (cost=4.30..10.83 rows=3 width=70)
   Recheck Cond: (lower((last_name)::text) = 'adam'::text)
   ->  Bitmap Index Scan on customer_lower_idx  (cost=0.00..4.30 rows=3 width=0)
         Index Cond: (lower((last_name)::text) = 'adam'::text)
(4 rows)

您会发现, PostgreSQL 优化器使用了索引。

结论

PostgreSQL 允许您在基于表中的一列或者多列的表达上是创建索引,这就是表达式索引。