PostgreSQL 10 - 理解其他b-tree功能

PostgreSQL 10 - 理解其他b-tree功能

  • 组合索引
  • 函数索引
  • 减少空间消耗
  • 建索引的时候添加数据

组合索引

一般来说,如果单一索引可以解决问题,就是最好的选择。你不可能把人们所有可能过滤的列都建索引。你只能使用组合索引,尽可能地提高性能。
比如有这样一张表:postal_code、first_name和last_name。电话薄会使用这样的组合索引。你会看到数据根据位置排序。位置相同,再根据名和姓排序。
下表显示,使用三个列的组合索引,可能的操作:

查询 可能 注释
postal_code = 2700 AND last_name = ‘san’ Yes AND first_name = ‘Li’ 索引的理想用例
postal_code = 2700 AND last_name = ‘san’ 无限制
last_name = ‘san’ AND postal_code = 2700 简单地交换条件
postal_code = 2700 和在postal_code列上的索引一样,但是组合索引更占空间
first_name = ‘Li’ 是,这是不同的用例 不能使用该索引的排序属性。不过,如果是非常宽的表,有很多列,PostgreSQL会扫描整个索引,因为比扫描表更节约成本

函数索引

PostgreSQL也支持函数索引-不是索引值,而是索引函数的输出。
比如下面的例子,可以给id列的余弦建索引:

postgres=# CREATE INDEX idx_cos ON t_random (cos(id));
CREATE INDEX
Time: 3359.743 ms (00:03.360)
postgres=# ANALYZE;
ANALYZE
Time: 1192.569 ms (00:01.193)

当然,只有输出不可变的函数,才可以建索引。比如

postgres=# SELECT age('2010-01-01 10:00:00'::timestamptz);
               age                
----------------------------------
 8 years 11 mons 19 days 14:00:00
(1 行记录)

像age这样的函数,就无法建索引,因为它的输出总在变化。PostgreSQL明确禁止输入相同输出缺不同的函数。
要测试上面的索引,我们写个简单的查询:

postgres=# EXPLAIN SELECT * FROM t_random WHERE cos(id) = 10;
                               QUERY PLAN                               
------------------------------------------------------------------------
 Index Scan using idx_cos on t_random  (cost=0.43..8.45 rows=1 width=9)
   Index Cond: (cos((id)::double precision) = '10'::double precision)
(2 行记录)

可以看到,该函数索引被使用了。

减少空间消耗

索引可以提升速度,但是带来了空间消耗。如果表包含1000万个整数值,对应的索引在逻辑上也包含这1000万个整数和额外的开销。
b-tree包含到每行的指针,我们看看索引消耗了多少空间:

postgres=# \di+
                             关联列表
 架构模式 |    名称    | 类型 |  拥有者  |  数据表  | 大小  | 描述 
----------+------------+------+----------+----------+-------+------
 public   | idx_cos    | 索引 | postgres | t_random | 86 MB | 
 public   | idx_id     | 索引 | postgres | t_test   | 86 MB | 
 public   | idx_name   | 索引 | postgres | t_test   | 86 MB | 
 public   | idx_random | 索引 | postgres | t_random | 86 MB | 
(4 行记录)

我的数据库,为了存储这些索引,用了344 MB。我们和表占用的空间比较一下:

postgres=# \d+
                             关联列表
 架构模式 |     名称      |  类型  |  拥有者  |    大小    | 描述 
----------+---------------+--------+----------+------------+------
 public   | t_random      | 数据表 | postgres | 169 MB     | 
 public   | t_test        | 数据表 | postgres | 169 MB     | 
 public   | t_test_id_seq | 序列数 | postgres | 8192 bytes | 
(3 行记录)

两张表才用了338 MB。换句话说,我们的索引比表还占用空间。
如果表里只有几个不同的值,可以使用部分索引:

postgres=# DROP INDEX idx_name;
DROP INDEX
postgres=# CREATE INDEX idx_name ON t_test (name)
postgres-# WHERE name NOT IN ('hans', 'paul');
CREATE INDEX

这样,大部分值被排除在外,可以享受一个小而有效的索引:

postgres=# \di+ idx_name
                              关联列表
 架构模式 |   名称   | 类型 |  拥有者  | 数据表 |    大小    | 描述 
----------+----------+------+----------+--------+------------+------
 public   | idx_name | 索引 | postgres | t_test | 8192 bytes | 
(1 行记录)

注意,只有排除掉构成表的大部分很频繁的数据才有意义(最少25%)。

建索引的时候添加数据

建索引很容易。不过,建索引的时候,不能修改表。CREATE INDEX命令会使用SHARE锁,确保不发生变化。对于小表,这没有问题。如果是生产系统中的大表,就是一个很严重的问题了。
一个解决办法是使用CREATE INDEX CONCURRENTLY命令,这样建索引的时候,就可以正常使用表了。

postgres=# CREATE INDEX CONCURRENTLY idx_name2 ON t_test (name);
CREATE INDEX

但是,如果你使用CREATE INDEX CONCURRENTLY命令,PostgreSQL不能保证成功。如果系统中的一些操作和这个建索引的操作相冲突,索引会最终标记为invalid。

你可能感兴趣的:(PostgreSQL)