在之前的文章中,我们讨论了PostgreSQL
索引引擎 ,访问方法的接口以及以下方法:
哈希索引 ,
B树 ,
GiST ,
SP-GiST ,
GIN和
RUM 。 本文的主题是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不在间隔内,因此我们更新最大值(请参见图)。 但是,如果新值仍在存储的限制内,则无需更新索引。

所有这些都与页面的新版本出现在可使用摘要信息的范围内的情况有关。 创建索引时,将为所有可用范围计算摘要信息,但是在进一步扩展表时,可能会出现超出限制的新页面。 这里有两个选项:
- 通常,索引不会立即更新。 这没什么大不了的:如前所述,在扫描索引时,将浏览整个范围。 实际更新是在“真空”期间完成的,也可以通过调用“ brin_summarize_new_values”函数手动完成。
- 如果我们使用“ autosummarize”参数创建索引,则更新将立即完成。 但是,当使用新值填充范围页面时,更新可能会经常发生,因此,此参数默认情况下处于关闭状态。
当出现新范围时,“ revmap”的大小可能会增加。 每当位于元页面和摘要数据之间的地图需要由另一页面扩展时,现有行版本就会移至其他页面。 因此,范围图始终位于元页面和摘要数据之间。
当
删除一行时,...什么也没有发生。 我们可以注意到,有时最小值或最大值将被删除,在这种情况下可以减小间隔。 但是要检测到这一点,我们将不得不读取该范围内的所有值,这是昂贵的。
索引的正确性不受影响,但是搜索可能需要查看比实际需要更多的范围。 通常,可以手动重新计算此类区域的摘要信息(通过调用“ brin_desummarize_range”和“ brin_summarize_new_values函数”),但是我们如何检测到这种需求? 无论如何,没有常规的程序可用于此目的。
最后,
更新一行只是删除过时的版本,而增加新的版本。
例子
让我们尝试为
演示数据库的表中的数据构建自己的小型数据仓库。 假设出于BI报告的目的,需要使用非规范化表格来反映从机场起飞或降落在机场的航班到机舱座位的准确性。 每个机场的数据每天都会在适当时区的午夜12点添加到表中。 数据将不会被更新或删除。
该表如下所示:
demo=# create table flights_bi( airport_code char(3), airport_coord point,
我们可以模拟使用嵌套循环加载数据的过程:一个外部循环-天(我们将考虑
一个大型数据库 ,因此为365天),一个内部循环-时区(从UTC + 02到UTC + 12) 。 该查询很长,并且没有特别的兴趣,因此我将其隐藏在扰流器下。
模拟将数据加载到存储中 DO $$ <<local>> 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 ( ;
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_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索引不包含对表行的引用,因此它的可用性不应完全阻止HOT更新,但它确实可以。
因此,BRIN主要设计用于根本没有更新或更新幅度很小的大型甚至大型表。 但是,它完美地应对了新行的增加(到表的末尾)。 这并不奇怪,因为创建此访问方法是为了查看数据仓库和分析报告。
我们需要选择什么大小的范围?
如果我们处理一个TB的表,那么在选择范围大小时,我们主要关心的可能不是使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个范围。 这似乎很现实:查询特定的一天,我们可以预期出现10%左右的错误。
让我们尝试:
demo=# create index on flights_bi using brin(scheduled_time);
索引的大小小至184 KB:
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页,但分布高度不一致:Petropavlovsk-Kamchatskiy和Anadyr只能容纳六页,而莫斯科及其附近地区则需要数百页。 范围的默认大小在这里不合适。 例如,将其设置为四个页面。
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
如我们所见,计划者使用了创建的索引。 它有多精确? 满足查询条件的行数(位图堆扫描节点的“行”)与使用索引返回的总行数(相同的值加上通过索引重新检查删除的行)之比告诉我们这一点。 在这种情况下,为83954 /(83954 + 12045),大约为预期值的90%(此值将一天到一天更改)。
位图索引扫描节点的“实际行”中的16640数字从何而来? 问题是该计划的节点构建了不准确的(逐页)位图,并且完全不知道该位图将触摸多少行,而需要显示某些内容。 因此,绝望地假设一页包含10行。 位图总共包含1664页(此值在“堆块:有损= 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=# 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大数
千倍 ! 但是,查询的执行速度更快:计划者使用统计信息来确定数据是物理排序的,不需要构建位图,并且主要是不需要重新检查索引条件:
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中工作,但我们无法建立点的索引:经度与时区紧密相关。 幸运的是,在将点转换为退化的矩形后,没有任何事情会妨碍在表达式上创建索引。 同时,我们将范围的大小设置为一页,以显示极限情况:
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)
看来
可以使用索引,但是计划者认为位图必须建立在整个表上(请看“位图索引扫描”节点的“行”),也就不足为奇了,计划者选择了顺序扫描。这种情况。 这里的问题是,对于几何类型,PostgreSQL不收集任何统计信息,并且计划者必须盲目行动:
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 |
las 但是没有对索引的抱怨-它确实可以正常工作:
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页)位于以下位置:
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)
下一个范围:
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”字母表示缺少空元素(第一个)和缺少不可合并的值(第二个)。 实际上,唯一不可合并的值是“ 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的能力。
继续阅读 。