在之前的文章中,我们讨论了PostgreSQL索引引擎、访问方法的接口,以及以下方法:哈希索引、B树、GiST、SP-GiST、GIN和RUM。本文的主题是BRIN索引。
BRIN
一般概念
与我们已经熟悉的索引不同,BRIN的想法是避免查看绝对不合适的行,而不是快速找到匹配的行。BRIN是一个不准确的索引:它根本不包含表行的TID。
简单地说,BRIN适用于值与其在表中的物理位置相关的列。换句话说,适用于没有ORDER BY子句的查询,但却以递增或递减的顺序返回列值(并且该列上没有索引)的数据。
这种访问方法是在欧洲大型分析数据库项目Axex的范围内创建的,着眼于几TB或几十TB大的表。BRIN的一个重要特性使我们能够在这样的表上创建索引,那就是它的小尺寸和最小的维护开销。
其工作原理如下。该表被拆分为多个页面大的范围(或多个块大的,这是相同的)(因此得名:块范围索引,Block Range Index,BRIN)。索引存储每个范围内数据的摘要信息。通常,这是最小值和最大值,但恰好不同(一个是最大值与最小值不同,一个是这个区间的最大值与最小值包含的范围与其他区间包含的范围不会完全重合),后面会说。假设执行了一个包含列条件的查询,如果搜索的值没有进入区间,则可以跳过整个范围,但如果它们在这个区间,那么所有块中的所有行都必须被检查一遍,以便从中选择匹配的行。
与其将BRIN视为索引,不如将其视为顺序扫描的加速器。如果我们将每个范围视为“虚拟”分区,我们可以将BRIN视为分区的替代。
现在让我们更详细地讨论索引的结构。
结构
第一个(更准确地说是第0个)页面包含元数据。
包含摘要信息的页面位于元数据的某个偏移位置。这些页面上的每个索引行都包含一个范围的摘要信息。
在元页面和摘要数据之间,有反向范围映射(缩写为“revmap”)的页面被定位。实际上,这是指向相应索引行的指针数组(TID)(这里应该理解为指向索引某一页的某一元组的指针,与指向数据页的某一元组的指针是一样,即TID)。
对于某些范围,“revmap”中的指针可能导致没有指向索引行(图中有一个指针用灰色标记)。在这种情况下,有范围被认为还没有摘要信息。
索引扫描
如果索引不包含对表行的引用,如何使用它?这种访问方法当然不能逐个TID返回行(上一段说的TID是指向索引行的指针,而不是指向数据行的指针。该索引只能用于查看一个值是否可能在表中,需要进一步顺序扫描,而不能直接返回目标值。注意逐个TID返回行只是按物理顺序返回,不是按数值排序返回),但它可以构建位图。位图页面可以有两种:精确到行和不精确到页面。使用的位图不准确。
算法很简单。按顺序扫描范围图(也就是说,范围按其在表中的位置顺序扫描)。指针用于确定索引行,其中包含每个范围的摘要信息(也就是先知道范围,再确定该范围对应的指针,指针其实和范围是一致的顺序,再根据指针指向的索引行知道该范围的摘要信息)。如果某个范围不包含所搜索的值,则跳过该范围,如果该范围可能包含该值(或摘要信息不可用),则该范围的所有页面都将添加到位图中。然后像往常一样使用生成的位图。
更新索引
更有趣的是,当表发生变化时,索引是如何更新的。
当向表页面添加一行的新版本时,我们确定它包含在哪个范围内,并使用范围映射来查找包含摘要信息的索引行。所有这些都是简单的算术运算。例如,假设一个范围的大小为4,在第13页出现了一个值为42的行版本。范围数(从零开始)为13/4=3,因此,在“revmap”中,我们取偏移量为3的指针(其顺序号为4)。
这个范围的最小值是31,最大值是40。由于新值42超出了区间,我们更新了最大值(见图)(所以范围会有一些重合)。但如果新值仍在存储的限制范围内,则不需要更新索引。
所有这些都与页面的新数据是否出现在摘要信息指定的范围内有关。创建索引时,会计算所有可用范围的摘要信息,但当数据表进一步扩展时,可能会出现超出限制的新页面(就是数据页面增加,旧范围包不下去)。这里有两个选择:
- 通常索引不会立即更新。这没什么大不了的:正如前面提到的,当扫描索引时,整个范围都会被扫描(就是暂时不生成新范围的摘要信息,但是当扫描该新范围时,会生成一个灰色指针,但它不指向任何摘要信息,这种范围也是会被完整扫描的)。实际更新是在“vacuum”期间完成的,也可以通过调用“brin_summary_new_values”函数手动完成。
- 如果我们使用“autosummarize”参数创建索引,更新将立即完成。但是,当使用新值填充该范围的页面时,更新可能会过于频繁,因此,默认情况下,此参数处于关闭状态。
当出现新范围时,“revmap”的大小可能会增加。每当位于元页面和摘要数据之间的映射需要扩展另一个页面时,现有的行版本就会移动到其他页面。因此,范围图总是位于元数据页和摘要数据之间。
当一行被删除时。。。什么都没发生。我们可以注意到,有时会删除最小值或最大值,在这种情况下,间隔可以缩短。但要检测到这一点,我们必须读取范围内的所有值,这是非常昂贵的。
索引的正确性不受影响,但搜索可能需要查看比实际需要更多的范围(检索时认为它可能出现在多个范围中,而实际范围比标的范围更小,所以其实只落在更少的范围里)。通常,可以手动重新计算此类范围的摘要信息(通过调用“brin_desummarize_range”和“brin_summarize_new_values”函数),但我们如何检测这种需求?无论如何,没有常规的程序可用于此目的。(这是一个很大的缺陷)
最后,更新一行只是删除过时的版本并添加一个新版本。
示例
让我们尝试为演示数据库表中的数据构建我们自己的迷你数据库。让我们假设,为了BI报告的目的,需要一个非规范化的表格来反映从机场起飞或降落在机场的航班,精确到机舱中的座位。每个机场的数据将在每天的午夜添加到表中一次。数据既不会更新也不会删除。
该表如下所示:
demo=# create table flights_bi( airport_code char(3), airport_coord point, -- geo coordinates of airport airport_utc_offset interval, -- time zone flight_no char(6), -- flight number flight_type text. -- flight type: departure / arrival scheduled_time timestamptz, -- scheduled departure/arrival time of flight actual_time timestamptz, -- actual time of flight aircraft_code char(3), seat_no varchar(4), -- seat number fare_conditions varchar(10), -- travel class passenger_id varchar(20), passenger_name text );
我们可以模拟使用嵌套循环加载数据的过程:外部为逐天循环(我们将考虑一个大数据库,因此循环365天),内部逐时区循环(从UTC + 02到UTC + 12)。这个查询很长,没有什么特别的意思,所以此处不展示代码。
demo=# select count(*) from flights_bi; count ---------- 30517076 (1 row) demo=# select pg_size_pretty(pg_total_relation_size('flights_bi')); pg_size_pretty ---------------- 4127 MB (1 row)
我们查询到3000万行和4GB。虽然尺寸不太大,但对于一台笔记本电脑来说足够了:顺序扫描花了我大约10秒钟。
我们应该在哪些列上创建索引?
由于BRIN索引的大小较小,开销适中,而且更新也不太频繁(如果有的话),因此出现了一个难得的机会,可以在所有字段上构建许多索引“以防万一”,例如,分析师用户可以在这些字段上创建临时查询。(有些列的索引)不会有用也没关系,但即使是效率不高的索引也肯定比顺序扫描更有效。当然,有些领域建立索引是绝对无用的;纯粹的常识会促使他们这样做。
但是,将我们自己局限于这条建议会很奇怪,因此,让我们尝试陈述一个更准确的标准。
我们已经提到,数据必须与其物理位置有一定的关联。这里有必要记住,PostgreSQL收集表列统计信息,其中包括相关值。planner使用该值在常规索引扫描和位图扫描之间进行选择,我们可以使用它来估计BRIN索引的适用性。
在上面的例子中,数据显然是按天排序的(按“计划时间”和“实际时间”——没有太大区别)。这是因为当向表中添加行时(不会删除和更新),它们会在文件中一个接一个地排列。在数据加载的模拟中,我们甚至没有使用ORDER BY子句,因此,一天内的日期通常可以以任意方式混合,但必须进行排序。让我们检查一下:
demo=# analyze flights_bi; demo=# select attname, correlation from pg_stats where tablename='flights_bi' order by correlation desc nulls last; attname | correlation --------------------+------------- scheduled_time | 0.999994 actual_time | 0.999994 fare_conditions | 0.796719 flight_type | 0.495937 airport_utc_offset | 0.438443 aircraft_code | 0.172262 airport_code | 0.0543143 flight_no | 0.0121366 seat_no | 0.00568042 passenger_name | 0.0046387 passenger_id | -0.00281272 airport_coord | (12 rows)
不太接近零的值(理想情况下,接近±1,如本例所示)表示BRIN索引是合适的。
旅行舱“fare_condition”(该列包含三个可选项)和航班类型“flight_type”(两个可选项)意外地出现在第二和第三位。这是一种错觉:从表面上看,相关性很高,而实际上,在连续的几页中,所有可能的值都肯定会遇到,这意味着使用BRIN不会有任何好处。
接下来是时区“airport_utc_offset”:在所考虑的示例中,在一天的周期内,航班按时区排序(就是按结构)。
我们将进一步试验这两个领域,时间和时区。
相关性可能减弱
当数据发生变化时,“按结构”放置的相关性很容易被削弱。这里的问题不在于对某个特定值的更改,而在于多版本并发控制的结构:过时的行版本在一个页面上被删除,但新版本可以在任何可用空间插入。因此,在更新过程中,所有行都会混淆。
我们可以通过减少“fillfactor”存储参数的值来部分控制这种效果,并通过这种方式在页面上留出可用空间,以便将来进行更新。但是我们并不想增加一张已经很大的表的尺寸。此外,这并不能解决删除问题:它们还通过释放现有页面中的某个位置来为新行“设置空位”。因此,本来会放置在文件末尾的行(就是放置在预留空间的行)将被插入到任意位置。
顺便说一句,这是一个奇怪的事实。因为BRIN索引不包含对表行的引用,所以它的可用性不应该妨碍热更新,但它确实会。
因此,BRIN主要是为大型甚至超大型的表格设计的,这些表格要么根本没有更新,要么更新得非常轻微(比如百度贴吧之类的不准挖坟)。但是,它可以完美地处理添加新行(到表的末尾)的问题。这并不奇怪,因为创建这种访问方法是为了数据仓库和分析报告。
选择多大的范围合适?
如果我们处理1TB的表,在选择范围大小时,我们主要关心的可能是不要使BRIN索引太大。然而,在上述的例子中,我们可以更准确地分析数据。
为此,我们可以选择列的某一值,并查看它们出现在多少页上。这些值的分布增加了应用BRIN索引的成功几率。此外,找到的页数将提示范围的大小。但是,如果该值“分散”在所有页面上,那么BRIN是无用的。
当然,我们应该使用这种技术,密切关注数据的内部结构。例如,考虑每个日期(更确切地说,时间戳,也包括时间)作为一个唯一的值是没有意义的-我们需要设置几天作为一个循环。
从技术上讲,这种分析可以通过查看隐藏的“ctid”列的值来完成,该列提供指向行版本(TID)的指针:页面编号和页面内的行编号。不幸的是,没有常规技术将TID分解为两个组件(即分解为页面编号与行编号),因此,我们必须通过文本表示转换类型:
demo=# select min(numblk), round(avg(numblk)) avg, max(numblk) from ( select count(distinct (ctid::text::point)[0]) numblk from flights_bi group by scheduled_time::date ) t; min | avg | max ------+------+------ 1192 | 1500 | 1796 (1 row) demo=# select relpages from pg_class where relname = 'flights_bi'; relpages ---------- 528172 (1 row)
(第一个查询的代码计算了每天的数据涉及的页的数量的最大值、最小值与平均值)
我们可以看到,每一天在页面上的分布相当均匀,而天数之间的分布略有混淆(就是某些页包含两天及以上的数据)(1500×365=547500,仅略大于表528172中的页数)。不管怎样,这一点实际上是明确的。
这里有价值的信息是特定页数。传统的范围大小为128页,每天将填充9-14个范围(1192/128~1796/128)。这似乎是符合实际的:对于特定日期的查询,我们可以预期大约10%的错误。
让我们试试:
demo=# create index on flights_bi using brin(scheduled_time);
索引的大小小到184KB:
demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_scheduled_time_idx')); pg_size_pretty ---------------- 184 kB (1 row)
在这种情况下,以失去精度为代价增加范围的尺寸几乎没有意义(如果增加范围的大小,那么整个索引会更小,但是这里已经很小了)。但是如果需要的话,我们可以减少范围的大小,来增加准确度(索引大小也会增加)。
现在让我们看看时区。在这里,我们也不能使用暴力手段。所有数值应除以日周期数,因为该分布在每天内重复。此外,由于时区很少,我们可以查看整个分布:
demo=# select airport_utc_offset, count(distinct (ctid::text::point)[0])/365 numblk from flights_bi group by airport_utc_offset order by 2; airport_utc_offset | numblk --------------------+-------- 12:00:00 | 6 06:00:00 | 8 02:00:00 | 10 11:00:00 | 13 08:00:00 | 28 09:00:00 | 29 10:00:00 | 40 04:00:00 | 47 07:00:00 | 110 05:00:00 | 231 03:00:00 | 932 (11 rows)
平均而言,每个时区的数据每天有133页,但分布非常不均匀:彼得罗巴甫洛夫斯克-堪察茨基和阿纳代尔的数据只有6页,而莫斯科及其周边地区需要数百页(就是每天飞到不同时区的飞机数随时区变化很大,但是起飞的飞机数每天分布很均匀)。在这里,范围的默认大小不好;例如,让我们将其设置为四页。
demo=# create index on flights_bi using brin(airport_utc_offset) with (pages_per_range=4); demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_airport_utc_offset_idx')); pg_size_pretty ---------------- 6528 kB (1 row)
执行计划
让我们看看我们的索引是如何工作的。让我们选择一周前的某一天(在演示数据库中,“今天”由“booking.now”功能决定):
demo=# \set d 'bookings.now()::date - interval \'7 days\'' demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '1 day'; QUERY PLAN -------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=10.282..94.328 rows=83954 loops=1) Recheck Cond: ... Rows Removed by Index Recheck: 12045 Heap Blocks: lossy=1664 -> Bitmap Index Scan on flights_bi_scheduled_time_idx (actual time=3.013..3.013 rows=16640 loops=1) Index Cond: ... Planning time: 0.375 ms Execution time: 97.805 ms
正如我们所见,规划器使用了创建的索引。它有多准确?符合查询条件的行数(“Bitmap Heap Scan ”节点的行)与使用索引返回的行总数的比率(相同的值加上Rows Removed by Index Recheck的行)告诉我们这一点。在本例中为83954/(83954+12045),约为预期的90%(该值会随目标日期变化)。
位图索引扫描节点的“实际行”中的数字16640来自哪里?问题是,计划的这个节点构建了一个不准确的(逐页)位图,并且完全不知道位图将接触多少行,同时需要显示一些内容。因此,在绝望中,假设一页包含10行。位图总共包含1664页(该值显示在“Heap Blocks:lossy=1664”中);所以,我们只得到16640。总而言之,这是一个毫无意义的数字,我们不应该注意。
使用航班构建的索引表现如何呢?例如,让我们以符拉迪沃斯托克的时区为例,该时区每天有28页:
demo=# explain (costs off,analyze) select * from flights_bi where airport_utc_offset = interval '8 hours'; QUERY PLAN ---------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=75.151..192.210 rows=587353 loops=1) Recheck Cond: (airport_utc_offset = '08:00:00'::interval) Rows Removed by Index Recheck: 191318 Heap Blocks: lossy=13380 -> Bitmap Index Scan on flights_bi_airport_utc_offset_idx (actual time=74.999..74.999 rows=133800 loops=1) Index Cond: (airport_utc_offset = '08:00:00'::interval) Planning time: 0.168 ms Execution time: 212.278 ms
规划器再次使用创建的BRIN索引。准确度更差(在这种情况下约为75%),但这是意料之中的,因为(数据与物理排序)相关性较低。
几个BRIN索引(就像任何其他索引一样)当然可以在位图级别联合(就是多索引加范围查询)。例如,以下是所选时区一个月的数据(请注意“BitmapAnd”节点):
demo=# \set d 'bookings.now()::date - interval \'60 days\'' demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '30 days' and airport_utc_offset = interval '8 hours'; QUERY PLAN --------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=62.046..113.849 rows=48154 loops=1) Recheck Cond: ... Rows Removed by Index Recheck: 18856 Heap Blocks: lossy=1152 -> BitmapAnd (actual time=61.777..61.777 rows=0 loops=1) -> Bitmap Index Scan on flights_bi_scheduled_time_idx (actual time=5.490..5.490 rows=435200 loops=1) Index Cond: ... -> Bitmap Index Scan on flights_bi_airport_utc_offset_idx (actual time=55.068..55.068 rows=133800 loops=1) Index Cond: ... Planning time: 0.408 ms Execution time: 115.475 ms
与B-树的比较
如果我们在与BRIN相同的字段上创建常规的B树索引呢?
demo=# create index flights_bi_scheduled_time_btree on flights_bi(scheduled_time); demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_scheduled_time_btree')); pg_size_pretty ---------------- 654 MB (1 row)
它似乎比我们的BRIN大几千倍!但是,查询的执行速度要快一点:planner使用统计数据来确定数据是按物理顺序排列的,不需要构建位图,主要是不需要重新检查索引条件:
demo=# explain (costs off,analyze) select * from flights_bi where scheduled_time >= :d and scheduled_time < :d + interval '1 day'; QUERY PLAN ---------------------------------------------------------------- Index Scan using flights_bi_scheduled_time_btree on flights_bi (actual time=0.099..79.416 rows=83954 loops=1) Index Cond: ... Planning time: 0.500 ms Execution time: 85.044 ms
这就是BRIN的美妙之处:我们牺牲了效率,却获得了很大的空间。
操作符类
极小极大
对于其值可以相互比较的数据类型,摘要信息由最小值和最大值组成。相应运算符类的名称包含“minmax”,例如“date_minmax_ops”。实际上,这些都是我们目前正在考虑的数据类型,大多数类型都是这种类型。
包含
并非所有数据类型都定义了比较运算符。例如,它们没有为代表机场地理坐标的点(“点”类型)定义。顺便说一句,正是因为这个原因,统计数据没有显示这一列的相关性。
demo=# select attname, correlation from pg_stats where tablename='flights_bi' and attname = 'airport_coord'; attname | correlation ---------------+------------- airport_coord | (1 row)
但许多这样的类型使我们能够引入“矩形框”的概念,例如,几何形状的矩形框。我们详细讨论了GiST索引如何使用此功能。类似地,BRIN还支持收集具有这些数据类型的列的摘要信息:范围内所有值的边界区域就是摘要值。
与GiST不同,BRIN的摘要值必须与被索引的值的类型相同。因此,我们无法建立点的索引,尽管很明显,坐标可以在BRIN中工作:经度与时区密切相关(就是经纬度可以,但是点不行,因为经纬度可以与时区相关,然后将BRIN建在时区上)。幸运的是,在将点转换为退化矩形后,没有什么可以阻止在表达式上创建索引。同时,我们将范围的大小设置为一页,以显示限制情况:
demo=# create index on flights_bi using brin (box(airport_coord)) with (pages_per_range=1);
即使在这种极端情况下,索引的大小也只有30 MB:
demo=# select pg_size_pretty(pg_total_relation_size('flights_bi_box_idx')); pg_size_pretty ---------------- 30 MB (1 row)
现在我们可以通过坐标限制机场进行查询。例如:
demo=# select airport_code, airport_name from airports where box(coordinates) <@ box '120,40,140,50'; airport_code | airport_name --------------+----------------- KHV | Khabarovsk-Novyi VVO | Vladivostok (2 rows)
然而,规划器将拒绝使用我们的索引。
demo=# analyze flights_bi; demo=# explain select * from flights_bi where box(airport_coord) <@ box '120,40,140,50'; QUERY PLAN --------------------------------------------------------------------- Seq Scan on flights_bi (cost=0.00..985928.14 rows=30517 width=111) Filter: (box(airport_coord) <@ '(140,50),(120,40)'::box)
为什么?让我们禁用顺序扫描,看看会发生什么:
demo=# set enable_seqscan = off; demo=# explain select * from flights_bi where box(airport_coord) <@ box '120,40,140,50'; QUERY PLAN -------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (cost=14079.67..1000007.81 rows=30517 width=111) Recheck Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) -> Bitmap Index Scan on flights_bi_box_idx (cost=0.00..14072.04 rows=30517076 width=0) Index Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box)
(主要这里没有analyse,cost是假设的,不是实际的)
似乎可以使用索引,但规划器假设位图必须构建在整个表上(Bitmap Index Scan节点的“行”),因此,规划器在这种情况下选择顺序扫描也就不足为奇了。这里的问题是,对于几何类型,PostgreSQL不收集任何统计数据,所以规划器一定是盲目的(就是只能假设将位图构建在整个表上,而不是只构建在需要索引的范围上。下面展示了PG没有收集以box数据类型构建的索引的信息):
demo=# select * from pg_stats where tablename = 'flights_bi_box_idx' \gx -[ RECORD 1 ]----------+------------------- schemaname | bookings tablename | flights_bi_box_idx attname | box inherited | f null_frac | 0 avg_width | 32 n_distinct | 0 most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram |
唉。但没有人抱怨该索引——它确实有效,而且效果很好:
demo=# explain (costs off,analyze) select * from flights_bi where box(airport_coord) <@ box '120,40,140,50'; QUERY PLAN ---------------------------------------------------------------------------------- Bitmap Heap Scan on flights_bi (actual time=158.142..315.445 rows=781790 loops=1) Recheck Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) Rows Removed by Index Recheck: 70726 Heap Blocks: lossy=14772 -> Bitmap Index Scan on flights_bi_box_idx (actual time=158.083..158.083 rows=147720 loops=1) Index Cond: (box(airport_coord) <@ '(140,50),(120,40)'::box) Planning time: 0.137 ms Execution time: 340.593 ms
结论必须是这样的:如果几何体需要任何特别的东西,就需要PostGIS。它可以收集统计数据。
内部构件
传统的扩展名“pageinspect”使我们能够查看BRIN索引内部。
首先,元信息将提示我们范围的大小以及为“revmap”分配的页面数:
demo=# select * from brin_metapage_info(get_raw_page('flights_bi_scheduled_time_idx',0)); magic | version | pagesperrange | lastrevmappage ------------+---------+---------------+---------------- 0xA8109CFA | 1 | 128 | 3 (1 row)
这里的第1-3页是为“revmap”分配的,而其余部分包含摘要数据。从“revmap”中,我们可以获得每个范围的摘要数据的参考。比如,包含前128页的第一个范围的信息位于此处:(第6页,offset为197,第4、5页是预留给之后扩展revmap的)
demo=# select * from brin_revmap_data(get_raw_page('flights_bi_scheduled_time_idx',1)) limit 1; pages --------- (6,197) (1 row)
这是摘要数据本身:
demo=# select allnulls, hasnulls, value from brin_page_items( get_raw_page('flights_bi_scheduled_time_idx',6), 'flights_bi_scheduled_time_idx' ) where itemoffset = 197; allnulls | hasnulls | value ----------+----------+---------------------------------------------------- f | f | {2016-08-15 02:45:00+03 .. 2016-08-15 17:15:00+03} (1 row)
下一个范围:
//定位TID所在页与行 demo=# select * from brin_revmap_data(get_raw_page('flights_bi_scheduled_time_idx',1)) offset 1 limit 1; pages --------- (6,198) (1 row) //得到该行包含的摘要信息 demo=# select allnulls, hasnulls, value from brin_page_items( get_raw_page('flights_bi_scheduled_time_idx',6), 'flights_bi_scheduled_time_idx' ) where itemoffset = 198; allnulls | hasnulls | value ----------+----------+---------------------------------------------------- f | f | {2016-08-15 06:00:00+03 .. 2016-08-15 18:55:00+03} (1 row)
等等。
对于“包含”类,“值”字段将显示如下内容
{(94.4005966186523,69.3110961914062),(77.6600036621,51.6693992614746) .. f .. f}
第一个值是嵌入矩形,末尾第一个“f”字母表示缺少空元素,第二个f表示缺少不可合并的值。实际上,唯一不可合并的值是“IPv4”和“IPv6”地址(“inet”数据类型)。
属性
我们之前已经提供了查询方法。
以下是访问方法的属性:
amname | name | pg_indexam_has_property --------+---------------+------------------------- brin | can_order | f brin | can_unique | f brin | can_multi_col | t brin | can_exclude | f
可以在多个列上创建索引。在这种情况下,它会为每一列收集自己的摘要统计信息,但它们会将每个范围存储在一起。当然,如果一个相同大小的范围适用于所有列,那么这个索引是有意义的。
以下索引层的特性:
name | pg_index_has_property ---------------+----------------------- clusterable | f index_scan | f bitmap_scan | t backward_scan | f
显然,只支持位图扫描。
然而,集群的缺乏似乎令人困惑。从表面上看,由于BRIN索引对行的物理顺序很敏感,因此能够根据索引对数据进行聚类是合乎逻辑的。但事实并非如此。我们只能创建一个“常规”索引(B树或GiST,取决于数据类型)并根据它进行聚类。顺便问一下,你愿意对一个巨大的表进行集群并同时考虑到独占锁、执行时间和重建期间的磁盘空间消耗吗?
以下是列层面的特性:
name | pg_index_column_has_property --------------------+------------------------------ asc | f desc | f nulls_first | f nulls_last | f orderable | f distance_orderable | f returnable | f search_array | f search_nulls | t
唯一可用的属性是操纵空值的能力。