原作者:Egor Rogov
翻译:魏波 编辑:孙祥斌
在之前的文章中,我们讨论了PostgreSQL 索引引擎,访问方法的接口以及以下方法:Hash索引,B树,GiST,SP-GiST,GIN和RUM。本文的主题是BRIN索引。
BRIN
一般概念
与我们已经熟悉的索引不同,BRIN的想法是避免浏览绝对不合适的行,而不是快速找到匹配的行。这始终是不准确的索引:它根本不包含表行的TID。简单地说,对于值与其在表中的物理位置相关的列,BRIN工作得很好。换句话说,如果一个没有ORDER BY子句的查询,实际上以递增或递减的顺序返回列值(并且该列上没有索引)。
此访问方法是在Axle 范围内创建的,Axle是一个用于大型分析数据库的欧洲项目,着眼于几TB或数十TB的表。BRIN的一项重要功能使我们能够在此类表上创建索引,它的体积小且维护开销最小。
其工作原理如下:表被分成是几页大的范围(或几个块大) 。因此BRIN又被称为:块范围索引。索引存储每个范围内数据的汇总信息,通常是最小值和最大值。假定执行的查询的条件包含列;如果所搜索的值未进入该间隔,则可以跳过整个范围;但如果确实定位到了它们,则必须仔细检查所有块中的所有行,以在其中选择匹配的行。
将BRIN视为索引,而不是顺序扫描的加速器。如果我们将每个范围视为“虚拟”分区,则可以将BRIN视为分区的替代方案。
现在让我们更详细地讨论索引的结构。
结构体
第一页(准确地说是零)包含元数据。 带有摘要信息的页面与元数据之间有一定的偏移量。这些页面上的每个索引行都包含一个范围的摘要信息。 在元页面和摘要数据之间,找到具有反向范围映射(简称为“ revmap”)的页面。实际上,这是指向相应索引行的指针(TID)的数组。 对于某些范围,«revmap»中的指针可能不会导致索引行(例如图中的灰色标记)。在这种情况下,该范围被认为还没有摘要信息。扫描引擎
如果索引不包含对表行的引用,该如何使用?此访问方法当然不能按TID返回TID行,但可以构建位图。位图页面可以有两种:精确到行,不精确到页。这是一个不准确的位图。
该 算法很简单: 范围映射是按顺序扫描的(即按照它们在表中位置的顺序进行扫描)。 指针用于确定具有每个范围的摘要信息的索引行。 如果一个范围不包含所寻求的值,则跳过它,如果范围可以包含该值(或摘要信息不可用),则将该范围的所有页面添加到位图中。 然后照常使用得到的位图。更新索引
更改表时如何更新索引更有趣。当将新版本的行添加到表页面时,我们确定该行包含在哪个范围中,并使用范围图查找包含摘要信息的索引行。这些都是简单的算术运算。例如,假设范围的大小为4,在第13页上,出现行值为42的行版本。范围的数字(从零开始)是13/4 = 3,因此,在《revmap》中,我们采用偏移量为3的指针(其顺序号为4)。
此范围的最小值为31,最大值为40。由于新值42不在间隔内,因此我们更新最大值(请参见图)。但是,如果新值仍在存储的限制内,则无需更新索引。 所有这些都与页面的新版本出现在摘要信息可用的范围内的情况有关。创建索引后,将为所有可用范围计算摘要信息,但是在进一步扩展表时,可能会出现超出限制的新页面。这里有两个选项: 1. 通常索引不会立即更新。这并不是什么大问题:如前所述,在扫描索引时,整个范围都将被浏览。实际的更新是在«vacuum»期间完成的,或者可以通过调用«brin_summarize_new_values»函数手动完成。 2. 如果我们使用«autosummarize»参数创建索引,则更新将立即完成。但是,当使用新值填充范围页面时,更新可能会经常发生,因此,此参数默认情况下处于关闭状态。 当 出现新范围时,«revmap»的大小可能会增加。 每当位于元页面和摘要数据之间的地图需要由另一页面扩展时,现有行版本就会移至其他页面。 因此,范围图始终位于元页面和摘要数据之间。 当删除一行时,…什么也没有发生。我们可以注意到,有时最小值或最大值将被删除,在这种情况下可以减小间隔。但是要检测到这一点,我们将不得不读取该范围内的所有值,这是昂贵的。 索引的正确性不受影响,但是搜索可能需要查看比实际需要更多的范围。通常,可以手动重新计算此类区域的摘要信息(通过调用«brin_desummarize_range»和«brin_summarize_new_values»函数),但是我们如何检测到这种需求?无论如何,没有常规的程序可用于此目的。 最后,更新一行只是删除过时的版本,而增加新的版本。
示例
让我们尝试为演示数据库的表中的数据构建自己的小型数据仓库。假设出于BI报告的目的,需要使用非规范化表格来反映从机场起 飞或降落在机场的航班的机舱座位准确性。每个机场的数据每天都会在适当时区的午夜12点添加到表中。数据将不再被更新或删除。 该表如 下所示:
demo=#createtable 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天数据的大库,下载地址https://edu.postgrespro.com/demo-big-en.zip),一个内部循环-按时区(从UTC + 02到UTC +12) 。
这个查询很长,如下所示(格式需注意调整,可参考原文):
DO $$<>DECLARE curdate date := (SELECT min(scheduled_departure) FROM flights); utc_offset interval;BEGIN WHILE (curdate <= bookings.now()::date) LOOP utc_offset := interval '12 hours'; WHILE (utc_offset >= interval '2 hours') LOOP INSERT INTO flights_bi WITH flight ( airport_code, airport_coord, flight_id, flight_no, scheduled_time, actual_time, aircraft_code, flight_type ) AS ( -- прибытия SELECT a.airport_code, a.coordinates, f.flight_id, f.flight_no, f.scheduled_departure, f.actual_departure, f.aircraft_code, 'departure' FROM airports a, flights f, pg_timezone_names tzn WHERE a.airport_code = f.departure_airport AND f.actual_departure IS NOT NULL AND tzn.name = a.timezone AND tzn.utc_offset = local.utc_offset AND timezone(a.timezone, f.actual_departure)::date = curdate UNION ALL -- вылеты SELECT a.airport_code, a.coordinates, f.flight_id, f.flight_no, f.scheduled_arrival, f.actual_arrival, f.aircraft_code, 'arrival' FROM airports a, flights f, pg_timezone_names tzn WHERE a.airport_code = f.arrival_airport AND f.actual_arrival IS NOT NULL AND tzn.name = a.timezone AND tzn.utc_offset = local.utc_offset AND timezone(a.timezone, f.actual_arrival)::date = curdate ) SELECT f.airport_code, f.airport_coord, local.utc_offset, f.flight_no, f.flight_type, f.scheduled_time, f.actual_time, f.aircraft_code, s.seat_no, s.fare_conditions, t.passenger_id, t.passenger_name FROM flight f JOIN seats s ON s.aircraft_code = f.aircraft_code LEFT JOIN boarding_passes bp ON bp.flight_id = f.flight_id AND bp.seat_no = s.seat_no LEFT JOIN ticket_flights tf ON tf.ticket_no = bp.ticket_no AND tf.flight_id = bp.flight_id LEFT JOIN tickets t ON t.ticket_no = tf.ticket_no; RAISE NOTICE '%, %', curdate, utc_offset; utc_offset := utc_offset - interval '1 hour'; END LOOP; curdate := curdate + 1; END LOOP;END;$$;
模拟加载数据到存储器
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万行和4 GB。
虽不算大,但足以用于笔记本电脑:
顺序扫描花了大约10秒钟。
问
我们应该在哪些列上创建索引?
由于BRIN索引的规模很小,开销也不大,更新也不经常发生(如果有的话),所以很少有机会在所有字段上构建许多索引,例如,分析师用户可以在这些字段上创建特别的查询。用的多少不重要,即使是一个不是很有效的索引也会比顺序扫描工作得更好。当然,常识也会告诉他们,在某些字段上构建索引是完全无用的。 将自己限制在这条建议上有点怪,让我们尝试提出一个更准确的标准。 我们已经提到过,数据必须在一定程度上与其物理位置相关。在这里要记住,PostgreSQL收集表列统计信息,其中包括相关值。计划器使用此值在常规索引扫描和位图扫描之间进行选择,我们可以使用它来估计BRIN索引的适用性。 在上面的示例中,数据显然是按天排序的(按“ scheduled_time”和“ actual_time”排序没有太大区别)。这是因为将行添加到表中(没有删除和更新)时,它们在文件中的布局是一个接一个的。在数据加载的模拟中,我们甚至没有使用ORDER BY子句,通常一天中的日期可以以任意方式混合,但是必须有序。让我们检查一下:
demo=#analyze flights_bi;demo=#select attname, correlation from pg_statswhere tablename='flights_bi' order by correlationdesc 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»:所考虑的示例中,在一天周期内,按时区
«by construction»对机场进行了排序。
我们将进一步试验这两个字段(时间和时区)。
可能削弱相关性
当数据更改时,“构造”位置的相关性很容易减弱。这里的问题不在于对某个特定值的更改,而
在于多版本并发控制的结构:在一个页面上删除过时的行版本,但是可以在任何可用的空闲
空间插入新版本。因此,更新时整行会混淆。
我们可以通过减少«fillfactor»存储参数的值来部分地控制这种效果,并通过这种方式为将来的更新在页面上留下空闲空间。但是我们想要增加一个已经很大的表的大小吗?此外,这并不能解决删除的问题:它们还通过释放现有页面内的空间为新行设置陷阱。因此,会将到达文件末尾的行插入到任意位置。
顺便说一下,这是一个奇怪的事实。由于BRIN索引不包含对表行的引用,因此它的可用性不
应完全阻止HOT更新,但它确实可以。
因此,BRIN的设计主要用于根本没有更新或更新幅度很小的大型表。但是,它完美地应对了
新行的增加(到表的末尾)。这并不奇怪,因为创建此访问方法是为了查看数据仓库和分析
报告。
问
我们需要选择多大的范围?
如果我们处理的是TB级的表,那么在选择范围大小时,我们主要关心的可能是不要使BRIN索
引太大。然而,在我们的情况下,可以更准确地分析数据。
为此,我们可以选择列的唯一值,并查看它们出现在多少页上。值的本地化增加了成功应用B
RIN索引的机会。此外,找到的页数将提示范围的大小。但是,如果该值在所有页面上都“分
散”,则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 relpagesfrom pg_classwhere relname ='flights_bi';
relpages
----------
528172
(1 row)
我们可以看到,每一天在页面上分布得相当均匀,并且每天间略有混淆(150*365 = 547500,
只比表中的页面数528172大一点)。此处的重要信息是特定数量的页面。使用128页的常规范围大小,每天将填充9-14个范围。这似乎很正常:查询特定的一天,我们可以预期出现10%左右的错误。咱们试试吧:
demo=#createindex on flights_biusing 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 numblkfrom flights_bi group by
airport_utc_offsetorder 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页,但分布高度不均匀:
Petropavlovsk-Kamchatskiy和Anadyr的数据仅有六页,而莫斯科及其附近地区则需要数百页。
范围的默认大小在这里不合适。例如,我们将其设置为四个页面:
demo=#createindex on flights_biusing 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
如我们所见,计划器使用了创建的索引。它有多精确?满足查询条件的行数(位图堆扫描节
点的“行”)与使用索引返回的总行数(相同的值加上通过索引重新检查删除的行)之比告诉我
们。在本例中,是83954 /(83954 + 12045),大约为预期值的90%(这个值会随着时间的
变化而更改)。
位图索引扫描节点的“实际行”中的16640数字来自哪里?问题是,这个计划的该节点构建了不
准确的(逐页)位图,并且完全不知道需要显示一些内容该位图将接触多少行。因此,绝望
地假设一页包含10行。位图总共包含1664页(该值在Heap Blocks: lossy=1664中显示);因此
,我们只得到16640。这是一个毫无意义的数字,不值得注意。
机场如何呢?例如,让我们以符拉迪沃斯托克(Vladivostok)的时区为例,该时区每天填充
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=#createindex flights_bi_scheduled_time_btree on flights_bi(schedu
led_time);
demo=#select pg_size_pretty(pg_total_relation_size('flights_bi_scheduled_time_btree'));
pg_size_pretty
----------------
654 MB
(1 row)
它看起来比我们的BRIN大数千倍!但是,查询的执行速度要快一些:计划器使用统计信息来确定数据是物理排序的,不需要构建位图,并且主要是不需要重新检查索引条件:
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
对于其值可以相互比较的数据类型,摘要信息由最小值和最大值组成。相应的运算符类别的名称包含«minmax»,例如«date_minmax_ops»。实际上,这些是我们目前考虑的数据类型,大多数类型都是这种类型。
inclusive
并非为所有数据类型都定义比较运算符。例如,没有为代表机场地理坐标的点(«point»类型)定义它们。顺便说一下,正是由于这个原因,统计信息并未显示此列的相关性。
demo=#select attname, correlationfrom pg_statswhere tablename='flights_
bi' and attname ='airport_coord';
attname | correlation
---------------+-------------
airport_coord |
(1 row)
但是,面对许多这种类型我们能够引入“边界区域”的概念,例如,几何形状的边界矩形。我们详细讨论了GiST索引如何使用此功能。类似地,BRIN还支持在具有以下数据类型的列上收集摘要信息:范围内所有值的边界区域就是摘要值。与GiST不同,BRIN的汇总值必须与被索引的值属于同一类型。因此,我们不能为点建立索引,尽管很明显,坐标可以在BRIN中工作:经度与时区紧密相连。幸运的是,在将点转换为简化的矩形后,没有任何东西会妨碍在表达式上创建索引。同时,我们将一个范围的大小设置为一个页面,只是为了显示极限情况:
demo=#createindex on flights_biusing 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_namefrom airportswhere box(coordina
tes)
airport_code | airport_name
--------------+-----------------
KHV | Khabarovsk-Novyi
VVO | Vladivostok
(2 rows)
但是,计划器将拒绝使用我们的索引。
demo=#analyze flights_bi;
demo=#explain select *from flights_biwhere box(airport_coord)
'120,40,140,50';
QUERY PLAN
---------------------------------------------------------------------
Seq Scan on flights_bi (cost=0.00..985928.14 rows=30517 width=111)
Filter: (box(airport_coord)
为什么?让我们禁用顺序扫描,看看会发生什么:
demo=#set enable_seqscan =off;
demo=#explain select *from flights_biwhere box(airport_coord)
'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)
-> Bitmap Index Scan on flights_bi_box_idx
(cost=0.00..14072.04 rows=30517076 width=0)
Index Cond: (box(airport_coord)
看来可以使用索引,但是计划器认为位图必须建立在整个表上(请看“Bitmap Index Scan”节
点的“行”),在这种情况下,选择顺序扫描也就不足为奇了。这里的问题是,对于几何类型,
PostgreSQL不收集任何统计信息,计划器必须盲目行动:
demo=#select *from pg_statswhere 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)
QUERY PLAN
----------------------------------------------------------------------------------
Bitmap Heap Scan on flights_bi (actual time=158.142..315.445 rows=781790 loops=1)
Recheck Cond: (box(airport_coord)
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)
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页,位于以下位置:
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, valuefrom 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)
下一个范围:
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, valuefrom 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)
对于«inclusion»类,«value»字段将显示类似的内容
{(94.4005966186523,69.3110961914062),(77.6600036621,51.6693992614746) .. 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
唯一可用的属性是操作NULL值的能力。 在下一篇中我们将继续一起探究Bloom索引。I Love PG
关于我们
中国开源软件推进联盟PostgreSQL分会(简称:PG分会)于2017年成立,由国内多家PG生态企业所共同发起,业务上接受工信部产业发展研究院指导。PG分会致力于构建PG产业生态,推动PG产学研用发展,是国内一家PG行业协会组织。
欢迎投稿
做你的舞台,show出自己的才华 。
投稿邮箱:partner@postgresqlchina.com
——愿能安放你不羁的灵魂
技术文章精彩回顾PostgreSQL学习的九层宝塔PostgreSQL职业发展与学习攻略搞懂PostgreSQL数据库透明数据加密之加密算法介绍一文读懂PostgreSQL-12分区表PostgreSQL源码学习之:RegularLockPostgresql源码学习之词法和语法分析PostgreSQL buffer管理最佳实践—PG数据库系统表空间重建PostgreSQL V12中的流复制配置2019,年度数据库舍 PostgreSQL 其谁?PostgreSQL使用分片(sharding)实现水平可扩展性一文搞懂PostgreSQL物化视图PostgreSQL原理解析之:PostgreSQL备机是否做checkpointPostgreSQL复制技术概述PG活动精彩回顾见证精彩|PostgresConf.CN2019大会盛大开幕PostgresConf.CN2019大会DAY2|三大分论坛,精彩不断PostgresConf.CN2019培训日|爆满!Training Day现场速递!「PCC-Training Day」培训日Day2圆满结束,PCC2019完美收官创建PG全球生态!PostgresConf.CN2019大会盛大召开首站起航!2019“让PG‘象’前行”上海站成功举行走进蓉城丨2019“让PG‘象’前行”成都站成功举行中国PG象牙塔计划发布,首批合作高校授牌仪式在天津举行PostgreSQL实训基地落户沈阳航空航天大学和渤海大学,高校数据库课改正当时群英论道聚北京,共话PostgreSQL相聚巴厘岛| PG Conf.Asia 2019 DAY0、DAY1简报相知巴厘岛| PG Conf.Asia 2019 DAY2简报相惜巴厘岛| PG Conf.Asia 2019 DAY3简报独家|硅谷Postgres大会简报全球规模最大的PostgreSQL会议等你来!PG培训认证精彩回顾关于中国PostgreSQL培训认证,你想知道的都在这里!首批中国PGCA培训圆满结束,首批认证考试将于10月18日和20日举行!中国首批PGCA认证考试圆满结束,203位考生成功获得认证!中国第二批PGCA认证考试圆满结束,115位考生喜获认证!请查收:中国首批PGCA证书!重要通知:三方共建,中国PostgreSQL认证权威升级!一场考试迎新年 | 12月28日,首次PGCE中级认证考试开考!近500人参与!首次PGCE中级、第三批次PGCA初级认证考试落幕!