B-tree索引类型,实现为“btree”访问方法,适用于可以排序的数据。换句话说,必须为数据类型定义“更大”、“更大或相等”、“更小”、“更小或相等”和“相等”操作符。
在B-tree的数据结构架构图中,B-tree的索引行被存在索引页中。在存储叶子节点的页中,这些行包含建立索引的数据(键)和指向表行的指针(TIDs)。在存储分支节点和根节点的页中,每行引用索引的一个子页,并包含该页中的最小值。
下面是一个使用整数键的字段索引的简化示例。
索引的第一页是一个元页,它引用索引根。内部节点位于根的下面,而叶页位于最底部行。向下箭头表示从叶节点到表行(TIDs)的引用。
B-trees 有以下重要特征:
- b树是平衡的,也就是说,每个叶页与根页之间用相同数量的内部页分隔。因此,搜索任何值都需要同样的时间。
- b树是多分支的,也就是说,每个页面(通常为8 KB)包含许多(数百)个TID。因此,b树的深度非常小,对于非常大的表,实际上可以达到4-5。
- 索引中的数据按非降序排序(页面之间和每个页面内部),相同级别的页面通过双向列表相互连接。因此,我们可以通过一个列表遍历一个或另一个方向,而不必每次都返回到根,来获得有序的数据集。
等值查询
让我们考虑在树中通过条件“index -field = expression”来搜索一个值。比如说,查询49。
搜索从根节点开始,我们需要确定要下降到哪个子节点。由于知道根节点(4,32,64)中的键,因此我们计算出子节点中的值范围。由于32≤49 < 64,我们需要下降到第二个子节点。接下来,重复相同的过程,直到我们到达一个叶节点,从中可以获得所需的TID。
非等值查询
当通过条件“indexed-field ≤ expression”(或“indexed-field ≥ expression”)进行搜索时,我们首先通过相等条件“indexed-field = expression”在索引中找到一个值(如果有的话),然后按照适当的方向遍历叶子页直到最后。下图以n ≤ 35为例:
">"和“<”以类似的方式,只是最初找到的值必须删除。
范围检索
当搜索范围“expression1≤indexd -field≤expression2”时,我们通过条件“indexd -field = expression1”找到一个值,然后在满足条件“indexd -field≤expression2”时继续遍历叶子页面;反之亦然:从第二个表达式开始,向相反的方向走,直到我们到达第一个表达式。下图以23 ≤ n ≤ 64为例:
多列索引:
演示数据:
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,这个顺序非常重要:页面内的数据将按第一个字段排序,然后按第二个字段排序,依此类推。大概结构如下:
从这个图表中可以清楚地看出,通过像“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)。