B-tree索引类型,实现为“btree”访问方法,适用于可以排序的数据。换句话说,必须为数据类型定义“更大”、“更大或相等”、“更小”、“更小或相等”和“相等”操作符。

在B-tree的数据结构架构图中,B-tree的索引行被存在索引页中。在存储叶子节点的页中,这些行包含建立索引的数据(键)和指向表行的指针(TIDs)。在存储分支节点和根节点的页中,每行引用索引的一个子页,并包含该页中的最小值。

下面是一个使用整数键的字段索引的简化示例。

pg索引 pg索引数据结构btree_搜索

索引的第一页是一个元页,它引用索引根。内部节点位于根的下面,而叶页位于最底部行。向下箭头表示从叶节点到表行(TIDs)的引用。

 

 B-trees 有以下重要特征:

  • b树是平衡的,也就是说,每个叶页与根页之间用相同数量的内部页分隔。因此,搜索任何值都需要同样的时间。
  • b树是多分支的,也就是说,每个页面(通常为8 KB)包含许多(数百)个TID。因此,b树的深度非常小,对于非常大的表,实际上可以达到4-5。
  • 索引中的数据按非降序排序(页面之间和每个页面内部),相同级别的页面通过双向列表相互连接。因此,我们可以通过一个列表遍历一个或另一个方向,而不必每次都返回到根,来获得有序的数据集。

等值查询

让我们考虑在树中通过条件“index -field = expression”来搜索一个值。比如说,查询49。

pg索引 pg索引数据结构btree_字段_02

 搜索从根节点开始,我们需要确定要下降到哪个子节点。由于知道根节点(4,32,64)中的键,因此我们计算出子节点中的值范围。由于32≤49 < 64,我们需要下降到第二个子节点。接下来,重复相同的过程,直到我们到达一个叶节点,从中可以获得所需的TID。

非等值查询

当通过条件“indexed-field ≤ expression”(或“indexed-field ≥ expression”)进行搜索时,我们首先通过相等条件“indexed-field = expression”在索引中找到一个值(如果有的话),然后按照适当的方向遍历叶子页直到最后。下图以n ≤ 35为例:

pg索引 pg索引数据结构btree_字段_03

 ">"和“<”以类似的方式,只是最初找到的值必须删除。

范围检索

当搜索范围“expression1≤indexd -field≤expression2”时,我们通过条件“indexd -field = expression1”找到一个值,然后在满足条件“indexd -field≤expression2”时继续遍历叶子页面;反之亦然:从第二个表达式开始,向相反的方向走,直到我们到达第一个表达式。下图以23 ≤ n ≤ 64为例:

pg索引 pg索引数据结构btree_字段_04

多列索引:

演示数据:

demo=# select * from aircrafts;
 aircraft_code |        model        | range 
---------------+---------------------+-------
 773           | Boeing 777-300      | 11100
 763           | Boeing 767-300      |  7900
 SU9           | Sukhoi SuperJet-100 |  3000
 320           | Airbus A320-200     |  5700
 321           | Airbus A321-200     |  5600
 319           | Airbus A319-100     |  6700
 733           | Boeing 737-300      |  4200
 CN1           | Cessna 208 Caravan  |  1200
 CR2           | Bombardier CRJ-200  |  2700
(9 rows)
demo=# create index aircrafts_case_asc_model_desc_idx on aircrafts(
 (case
    when range < 4000 then 1
    when range < 10000 then 2
    else 3
  end) ASC,
  model DESC);

demo=# explain(costs off)
select class, model from aircrafts_v order by class ASC, model DESC;
QUERY PLAN                            
-----------------------------------------------------------------
 Index Scan using aircrafts_case_asc_model_desc_idx on aircrafts
(1 row)

使用多列索引时出现的另一个问题是索引中列出列的顺序。对于B-tree,这个顺序非常重要:页面内的数据将按第一个字段排序,然后按第二个字段排序,依此类推。大概结构如下:

pg索引 pg索引数据结构btree_子节点_05

 从这个图表中可以清楚地看出,通过像“class = 3”(仅通过第一个字段进行搜索)或“class = 3 and model =‘波音777-300’”(通过两个字段进行搜索)这样的谓词进行搜索将有效地工作。

然而,通过谓词“model = 'Boeing 777-300'”进行搜索的效率要低得多:从根开始,我们不能确定要下降到哪一个子节点,因此,我们必须下降到所有的子节点。这并不意味着这样的索引永远不会被使用——它的效率是有争议的。例如,如果我们有三个级别的飞机,每个级别有很多型号,我们将不得不浏览大约三分之一的索引,这可能效率还不如全表扫描;

因此在实际使用过程中如果既有class = 3,class = 3 and model =‘波音777-300’,model =‘波音777-300’三类查询,一般为(class,model)和model建两个索引。

 

NULLs

Btree支持用is null 和is not null扫描索引。

demo=# create index on flights(actual_arrival);

demo=# explain(costs off) select * from flights where actual_arrival is null;
QUERY PLAN                       
-------------------------------------------------------
 Bitmap Heap Scan on flights
   Recheck Cond: (actual_arrival IS NULL)
   ->  Bitmap Index Scan on flights_actual_arrival_idx
         Index Cond: (actual_arrival IS NULL)
(4 rows)

空值位于叶节点的一端或另一端,这取决于索引是如何创建的(NULLS FIRST or NULLS LAST)。如果查询包含排序,这一点很重要:如果SELECT命令在其order BY子句中指定的空值顺序与构建索引指定的顺序相同,则可以使用索引(NULLS FIRST or NULLS LAST)。