日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

PostgreSQL中的索引—9(BRIN)

發(fā)布時間:2023/12/16 数据库 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PostgreSQL中的索引—9(BRIN) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在之前的文章中,我們討論了PostgreSQL索引引擎、訪問方法的接口,以及以下方法:哈希索引、B樹、GiST、SP-GiST、GIN和RUM。本文的主題是BRIN索引。

BRIN

一般概念

與我們已經(jīng)熟悉的索引不同,BRIN的想法是避免查看絕對不合適的行,而不是快速找到匹配的行。BRIN是一個不準(zhǔn)確的索引:它根本不包含表行的TID。

簡單地說,BRIN適用于值與其在表中的物理位置相關(guān)的列。換句話說,適用于沒有ORDER BY子句的查詢,但卻以遞增或遞減的順序返回列值(并且該列上沒有索引)的數(shù)據(jù)。

這種訪問方法是在歐洲大型分析數(shù)據(jù)庫項目Axex的范圍內(nèi)創(chuàng)建的,著眼于幾TB或幾十TB大的表。BRIN的一個重要特性使我們能夠在這樣的表上創(chuàng)建索引,那就是它的小尺寸和最小的維護(hù)開銷。

其工作原理如下。該表被拆分為多個頁面大的范圍(或多個塊大的,這是相同的)(因此得名:塊范圍索引,Block Range Index,BRIN)。索引存儲每個范圍內(nèi)數(shù)據(jù)的摘要信息。通常,這是最小值和最大值,但恰好不同(一個是最大值與最小值不同,一個是這個區(qū)間的最大值與最小值包含的范圍與其他區(qū)間包含的范圍不會完全重合),后面會說。假設(shè)執(zhí)行了一個包含列條件的查詢,如果搜索的值沒有進(jìn)入?yún)^(qū)間,則可以跳過整個范圍,但如果它們在這個區(qū)間,那么所有塊中的所有行都必須被檢查一遍,以便從中選擇匹配的行。

與其將BRIN視為索引,不如將其視為順序掃描的加速器。如果我們將每個范圍視為“虛擬”分區(qū),我們可以將BRIN視為分區(qū)的替代。

現(xiàn)在讓我們更詳細(xì)地討論索引的結(jié)構(gòu)。

結(jié)構(gòu)

第一個(更準(zhǔn)確地說是第0個)頁面包含元數(shù)據(jù)。

包含摘要信息的頁面位于元數(shù)據(jù)的某個偏移位置。這些頁面上的每個索引行都包含一個范圍的摘要信息。

在元頁面和摘要數(shù)據(jù)之間,有反向范圍映射(縮寫為“revmap”)的頁面被定位。實際上,這是指向相應(yīng)索引行的指針數(shù)組(TID)(這里應(yīng)該理解為指向索引某一頁的某一元組的指針,與指向數(shù)據(jù)頁的某一元組的指針是一樣,即TID)。

?對于某些范圍,“revmap”中的指針可能導(dǎo)致沒有指向索引行(圖中有一個指針用灰色標(biāo)記)。在這種情況下,有范圍被認(rèn)為還沒有摘要信息。

索引掃描

如果索引不包含對表行的引用,如何使用它?這種訪問方法當(dāng)然不能逐個TID返回行(上一段說的TID是指向索引行的指針,而不是指向數(shù)據(jù)行的指針。該索引只能用于查看一個值是否可能在表中,需要進(jìn)一步順序掃描,而不能直接返回目標(biāo)值。注意逐個TID返回行只是按物理順序返回,不是按數(shù)值排序返回),但它可以構(gòu)建位圖。位圖頁面可以有兩種:精確到行和不精確到頁面。使用的位圖不準(zhǔn)確。

算法很簡單。按順序掃描范圍圖(也就是說,范圍按其在表中的位置順序掃描)。指針用于確定索引行,其中包含每個范圍的摘要信息(也就是先知道范圍,再確定該范圍對應(yīng)的指針,指針其實和范圍是一致的順序,再根據(jù)指針指向的索引行知道該范圍的摘要信息)。如果某個范圍不包含所搜索的值,則跳過該范圍,如果該范圍可能包含該值(或摘要信息不可用),則該范圍的所有頁面都將添加到位圖中。然后像往常一樣使用生成的位圖。

更新索引

更有趣的是,當(dāng)表發(fā)生變化時,索引是如何更新的。

當(dāng)向表頁面添加一行的新版本時,我們確定它包含在哪個范圍內(nèi),并使用范圍映射來查找包含摘要信息的索引行。所有這些都是簡單的算術(shù)運算。例如,假設(shè)一個范圍的大小為4,在第13頁出現(xiàn)了一個值為42的行版本。范圍數(shù)(從零開始)為13/4=3,因此,在“revmap”中,我們?nèi)∑屏繛?的指針(其順序號為4)。

這個范圍的最小值是31,最大值是40。由于新值42超出了區(qū)間,我們更新了最大值(見圖)(所以范圍會有一些重合)。但如果新值仍在存儲的限制范圍內(nèi),則不需要更新索引。

所有這些都與頁面的新數(shù)據(jù)是否出現(xiàn)在摘要信息指定的范圍內(nèi)有關(guān)。創(chuàng)建索引時,會計算所有可用范圍的摘要信息,但當(dāng)數(shù)據(jù)表進(jìn)一步擴(kuò)展時,可能會出現(xiàn)超出限制的新頁面(就是數(shù)據(jù)頁面增加,舊范圍包不下去)。這里有兩個選擇:

  • 通常索引不會立即更新。這沒什么大不了的:正如前面提到的,當(dāng)掃描索引時,整個范圍都會被掃描(就是暫時不生成新范圍的摘要信息,但是當(dāng)掃描該新范圍時,會生成一個灰色指針,但它不指向任何摘要信息,這種范圍也是會被完整掃描的)。實際更新是在“vacuum”期間完成的,也可以通過調(diào)用“brin_summary_new_values”函數(shù)手動完成。
  • 如果我們使用“autosummarize”參數(shù)創(chuàng)建索引,更新將立即完成。但是,當(dāng)使用新值填充該范圍的頁面時,更新可能會過于頻繁,因此,默認(rèn)情況下,此參數(shù)處于關(guān)閉狀態(tài)。
  • 當(dāng)出現(xiàn)新范圍時,“revmap”的大小可能會增加。每當(dāng)位于元頁面和摘要數(shù)據(jù)之間的映射需要擴(kuò)展另一個頁面時,現(xiàn)有的行版本就會移動到其他頁面。因此,范圍圖總是位于元數(shù)據(jù)頁和摘要數(shù)據(jù)之間。

    當(dāng)一行被刪除時。。。什么都沒發(fā)生。我們可以注意到,有時會刪除最小值或最大值,在這種情況下,間隔可以縮短。但要檢測到這一點,我們必須讀取范圍內(nèi)的所有值,這是非常昂貴的。

    索引的正確性不受影響,但搜索可能需要查看比實際需要更多的范圍(檢索時認(rèn)為它可能出現(xiàn)在多個范圍中,而實際范圍比標(biāo)的范圍更小,所以其實只落在更少的范圍里)。通常,可以手動重新計算此類范圍的摘要信息(通過調(diào)用“brin_desummarize_range”和“brin_summarize_new_values”函數(shù)),但我們?nèi)绾螜z測這種需求?無論如何,沒有常規(guī)的程序可用于此目的。(這是一個很大的缺陷)

    最后,更新一行只是刪除過時的版本并添加一個新版本。

    示例

    讓我們嘗試為演示數(shù)據(jù)庫表中的數(shù)據(jù)構(gòu)建我們自己的迷你數(shù)據(jù)庫。讓我們假設(shè),為了BI報告的目的,需要一個非規(guī)范化的表格來反映從機(jī)場起飛或降落在機(jī)場的航班,精確到機(jī)艙中的座位。每個機(jī)場的數(shù)據(jù)將在每天的午夜添加到表中一次。數(shù)據(jù)既不會更新也不會刪除。

    該表如下所示:

    demo=# create table flights_bi(airport_code char(3),airport_coord point, -- geo coordinates of airportairport_utc_offset interval, -- time zoneflight_no char(6), -- flight numberflight_type text. -- flight type: departure / arrival scheduled_time timestamptz, -- scheduled departure/arrival time of flightactual_time timestamptz, -- actual time of flightaircraft_code char(3),seat_no varchar(4), -- seat numberfare_conditions varchar(10), -- travel classpassenger_id varchar(20),passenger_name text );

    我們可以模擬使用嵌套循環(huán)加載數(shù)據(jù)的過程:外部為逐天循環(huán)(我們將考慮一個大數(shù)據(jù)庫,因此循環(huán)365天),內(nèi)部逐時區(qū)循環(huán)(從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秒鐘。

    我們應(yīng)該在哪些列上創(chuàng)建索引?

    由于BRIN索引的大小較小,開銷適中,而且更新也不太頻繁(如果有的話),因此出現(xiàn)了一個難得的機(jī)會,可以在所有字段上構(gòu)建許多索引“以防萬一”,例如,分析師用戶可以在這些字段上創(chuàng)建臨時查詢。(有些列的索引)不會有用也沒關(guān)系,但即使是效率不高的索引也肯定比順序掃描更有效。當(dāng)然,有些領(lǐng)域建立索引是絕對無用的;純粹的常識會促使他們這樣做。

    但是,將我們自己局限于這條建議會很奇怪,因此,讓我們嘗試陳述一個更準(zhǔn)確的標(biāo)準(zhǔn)。

    我們已經(jīng)提到,數(shù)據(jù)必須與其物理位置有一定的關(guān)聯(lián)。這里有必要記住,PostgreSQL收集表列統(tǒng)計信息,其中包括相關(guān)值。planner使用該值在常規(guī)索引掃描和位圖掃描之間進(jìn)行選擇,我們可以使用它來估計BRIN索引的適用性。

    在上面的例子中,數(shù)據(jù)顯然是按天排序的(按“計劃時間”和“實際時間”——沒有太大區(qū)別)。這是因為當(dāng)向表中添加行時(不會刪除和更新),它們會在文件中一個接一個地排列。在數(shù)據(jù)加載的模擬中,我們甚至沒有使用ORDER BY子句,因此,一天內(nèi)的日期通常可以以任意方式混合,但必須進(jìn)行排序。讓我們檢查一下:

    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.999994actual_time | 0.999994fare_conditions | 0.796719flight_type | 0.495937airport_utc_offset | 0.438443aircraft_code | 0.172262airport_code | 0.0543143flight_no | 0.0121366seat_no | 0.00568042passenger_name | 0.0046387passenger_id | -0.00281272airport_coord | (12 rows)

    不太接近零的值(理想情況下,接近±1,如本例所示)表示BRIN索引是合適的。

    旅行艙“fare_condition”(該列包含三個可選項)和航班類型“flight_type”(兩個可選項)意外地出現(xiàn)在第二和第三位。這是一種錯覺:從表面上看,相關(guān)性很高,而實際上,在連續(xù)的幾頁中,所有可能的值都肯定會遇到,這意味著使用BRIN不會有任何好處。

    接下來是時區(qū)“airport_utc_offset”:在所考慮的示例中,在一天的周期內(nèi),航班按時區(qū)排序(就是按結(jié)構(gòu))。

    我們將進(jìn)一步試驗這兩個領(lǐng)域,時間和時區(qū)。

    相關(guān)性可能減弱

    當(dāng)數(shù)據(jù)發(fā)生變化時,“按結(jié)構(gòu)”放置的相關(guān)性很容易被削弱。這里的問題不在于對某個特定值的更改,而在于多版本并發(fā)控制的結(jié)構(gòu):過時的行版本在一個頁面上被刪除,但新版本可以在任何可用空間插入。因此,在更新過程中,所有行都會混淆。

    我們可以通過減少“fillfactor”存儲參數(shù)的值來部分控制這種效果,并通過這種方式在頁面上留出可用空間,以便將來進(jìn)行更新。但是我們并不想增加一張已經(jīng)很大的表的尺寸。此外,這并不能解決刪除問題:它們還通過釋放現(xiàn)有頁面中的某個位置來為新行“設(shè)置空位”。因此,本來會放置在文件末尾的行(就是放置在預(yù)留空間的行)將被插入到任意位置。

    順便說一句,這是一個奇怪的事實。因為BRIN索引不包含對表行的引用,所以它的可用性不應(yīng)該妨礙熱更新,但它確實會。

    因此,BRIN主要是為大型甚至超大型的表格設(shè)計的,這些表格要么根本沒有更新,要么更新得非常輕微(比如百度貼吧之類的不準(zhǔn)挖墳)。但是,它可以完美地處理添加新行(到表的末尾)的問題。這并不奇怪,因為創(chuàng)建這種訪問方法是為了數(shù)據(jù)倉庫和分析報告。

    選擇多大的范圍合適?

    如果我們處理1TB的表,在選擇范圍大小時,我們主要關(guān)心的可能是不要使BRIN索引太大。然而,在上述的例子中,我們可以更準(zhǔn)確地分析數(shù)據(jù)。

    為此,我們可以選擇列的某一值,并查看它們出現(xiàn)在多少頁上。這些值的分布增加了應(yīng)用BRIN索引的成功幾率。此外,找到的頁數(shù)將提示范圍的大小。但是,如果該值“分散”在所有頁面上,那么BRIN是無用的。

    當(dāng)然,我們應(yīng)該使用這種技術(shù),密切關(guān)注數(shù)據(jù)的內(nèi)部結(jié)構(gòu)。例如,考慮每個日期(更確切地說,時間戳,也包括時間)作為一個唯一的值是沒有意義的-我們需要設(shè)置幾天作為一個循環(huán)。

    從技術(shù)上講,這種分析可以通過查看隱藏的“ctid”列的值來完成,該列提供指向行版本(TID)的指針:頁面編號和頁面內(nèi)的行編號。不幸的是,沒有常規(guī)技術(shù)將TID分解為兩個組件(即分解為頁面編號與行編號),因此,我們必須通過文本表示轉(zhuǎn)換類型:

    demo=# select min(numblk), round(avg(numblk)) avg, max(numblk) from ( select count(distinct (ctid::text::point)[0]) numblkfrom flights_bigroup 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)

    (第一個查詢的代碼計算了每天的數(shù)據(jù)涉及的頁的數(shù)量的最大值、最小值與平均值)

    我們可以看到,每一天在頁面上的分布相當(dāng)均勻,而天數(shù)之間的分布略有混淆(就是某些頁包含兩天及以上的數(shù)據(jù))(1500×365=547500,僅略大于表528172中的頁數(shù))。不管怎樣,這一點實際上是明確的。

    這里有價值的信息是特定頁數(shù)。傳統(tǒng)的范圍大小為128頁,每天將填充9-14個范圍(1192/128~1796/128)。這似乎是符合實際的:對于特定日期的查詢,我們可以預(yù)期大約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)

    在這種情況下,以失去精度為代價增加范圍的尺寸幾乎沒有意義(如果增加范圍的大小,那么整個索引會更小,但是這里已經(jīng)很小了)。但是如果需要的話,我們可以減少范圍的大小,來增加準(zhǔn)確度(索引大小也會增加)。

    現(xiàn)在讓我們看看時區(qū)。在這里,我們也不能使用暴力手段。所有數(shù)值應(yīng)除以日周期數(shù),因為該分布在每天內(nèi)重復(fù)。此外,由于時區(qū)很少,我們可以查看整個分布:

    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 | 606:00:00 | 802:00:00 | 1011:00:00 | 1308:00:00 | 2809:00:00 | 2910:00:00 | 4004:00:00 | 4707:00:00 | 11005:00:00 | 23103:00:00 | 932 (11 rows)

    平均而言,每個時區(qū)的數(shù)據(jù)每天有133頁,但分布非常不均勻:彼得羅巴甫洛夫斯克-堪察茨基和阿納代爾的數(shù)據(jù)只有6頁,而莫斯科及其周邊地區(qū)需要數(shù)百頁(就是每天飛到不同時區(qū)的飛機(jī)數(shù)隨時區(qū)變化很大,但是起飛的飛機(jī)數(shù)每天分布很均勻)。在這里,范圍的默認(rèn)大小不好;例如,讓我們將其設(shè)置為四頁。

    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)

    執(zhí)行計劃

    讓我們看看我們的索引是如何工作的。讓我們選擇一周前的某一天(在演示數(shù)據(jù)庫中,“今天”由“booking.now”功能決定):

    demo=# \set d 'bookings.now()::date - interval \'7 days\'' demo=# explain (costs off,analyze)select *from flights_biwhere 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: 12045Heap 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 msExecution time: 97.805 ms

    正如我們所見,規(guī)劃器使用了創(chuàng)建的索引。它有多準(zhǔn)確?符合查詢條件的行數(shù)(“Bitmap Heap Scan ”節(jié)點的行)與使用索引返回的行總數(shù)的比率(相同的值加上Rows Removed by Index Recheck的行)告訴我們這一點。在本例中為83954/(83954+12045),約為預(yù)期的90%(該值會隨目標(biāo)日期變化)。

    位圖索引掃描節(jié)點的“實際行”中的數(shù)字16640來自哪里?問題是,計劃的這個節(jié)點構(gòu)建了一個不準(zhǔn)確的(逐頁)位圖,并且完全不知道位圖將接觸多少行,同時需要顯示一些內(nèi)容。因此,在絕望中,假設(shè)一頁包含10行。位圖總共包含1664頁(該值顯示在“Heap Blocks:lossy=1664”中);所以,我們只得到16640。總而言之,這是一個毫無意義的數(shù)字,我們不應(yīng)該注意。

    使用航班構(gòu)建的索引表現(xiàn)如何呢?例如,讓我們以符拉迪沃斯托克的時區(qū)為例,該時區(qū)每天有28頁:

    demo=# explain (costs off,analyze)select *from flights_biwhere 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: 191318Heap 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 msExecution time: 212.278 ms

    規(guī)劃器再次使用創(chuàng)建的BRIN索引。準(zhǔn)確度更差(在這種情況下約為75%),但這是意料之中的,因為(數(shù)據(jù)與物理排序)相關(guān)性較低。

    幾個BRIN索引(就像任何其他索引一樣)當(dāng)然可以在位圖級別聯(lián)合(就是多索引加范圍查詢)。例如,以下是所選時區(qū)一個月的數(shù)據(jù)(請注意“BitmapAnd”節(jié)點):

    demo=# \set d 'bookings.now()::date - interval \'60 days\'' demo=# explain (costs off,analyze)select *from flights_biwhere 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: 18856Heap 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 msExecution time: 115.475 ms

    與B-樹的比較

    如果我們在與BRIN相同的字段上創(chuàng)建常規(guī)的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大幾千倍!但是,查詢的執(zhí)行速度要快一點:planner使用統(tǒng)計數(shù)據(jù)來確定數(shù)據(jù)是按物理順序排列的,不需要構(gòu)建位圖,主要是不需要重新檢查索引條件:

    demo=# explain (costs off,analyze)select *from flights_biwhere 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 msExecution time: 85.044 ms

    這就是BRIN的美妙之處:我們犧牲了效率,卻獲得了很大的空間。

    操作符類

    極小極大

    對于其值可以相互比較的數(shù)據(jù)類型,摘要信息由最小值和最大值組成。相應(yīng)運算符類的名稱包含“minmax”,例如“date_minmax_ops”。實際上,這些都是我們目前正在考慮的數(shù)據(jù)類型,大多數(shù)類型都是這種類型。

    包含

    并非所有數(shù)據(jù)類型都定義了比較運算符。例如,它們沒有為代表機(jī)場地理坐標(biāo)的點(“點”類型)定義。順便說一句,正是因為這個原因,統(tǒng)計數(shù)據(jù)沒有顯示這一列的相關(guān)性。

    demo=# select attname, correlation from pg_stats where tablename='flights_bi' and attname = 'airport_coord';attname | correlation ---------------+-------------airport_coord | (1 row)

    但許多這樣的類型使我們能夠引入“矩形框”的概念,例如,幾何形狀的矩形框。我們詳細(xì)討論了GiST索引如何使用此功能。類似地,BRIN還支持收集具有這些數(shù)據(jù)類型的列的摘要信息:范圍內(nèi)所有值的邊界區(qū)域就是摘要值。

    與GiST不同,BRIN的摘要值必須與被索引的值的類型相同。因此,我們無法建立點的索引,盡管很明顯,坐標(biāo)可以在BRIN中工作:經(jīng)度與時區(qū)密切相關(guān)(就是經(jīng)緯度可以,但是點不行,因為經(jīng)緯度可以與時區(qū)相關(guān),然后將BRIN建在時區(qū)上)。幸運的是,在將點轉(zhuǎn)換為退化矩形后,沒有什么可以阻止在表達(dá)式上創(chuàng)建索引。同時,我們將范圍的大小設(shè)置為一頁,以顯示限制情況:

    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)

    現(xiàn)在我們可以通過坐標(biāo)限制機(jī)場進(jìn)行查詢。例如:

    demo=# select airport_code, airport_name from airports where box(coordinates) <@ box '120,40,140,50';airport_code | airport_name --------------+-----------------KHV | Khabarovsk-NovyiVVO | Vladivostok (2 rows)

    然而,規(guī)劃器將拒絕使用我們的索引。

    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)

    為什么?讓我們禁用順序掃描,看看會發(fā)生什么:

    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是假設(shè)的,不是實際的)

    似乎可以使用索引,但規(guī)劃器假設(shè)位圖必須構(gòu)建在整個表上(Bitmap Index Scan節(jié)點的“行”),因此,規(guī)劃器在這種情況下選擇順序掃描也就不足為奇了。這里的問題是,對于幾何類型,PostgreSQL不收集任何統(tǒng)計數(shù)據(jù),所以規(guī)劃器一定是盲目的(就是只能假設(shè)將位圖構(gòu)建在整個表上,而不是只構(gòu)建在需要索引的范圍上。下面展示了PG沒有收集以box數(shù)據(jù)類型構(gòu)建的索引的信息):

    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: 70726Heap 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 msExecution time: 340.593 ms

    結(jié)論必須是這樣的:如果幾何體需要任何特別的東西,就需要PostGIS。它可以收集統(tǒng)計數(shù)據(jù)。

    內(nèi)部構(gòu)件

    傳統(tǒng)的擴(kuò)展名“pageinspect”使我們能夠查看BRIN索引內(nèi)部。

    首先,元信息將提示我們范圍的大小以及為“revmap”分配的頁面數(shù):

    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”分配的,而其余部分包含摘要數(shù)據(jù)。從“revmap”中,我們可以獲得每個范圍的摘要數(shù)據(jù)的參考。比如,包含前128頁的第一個范圍的信息位于此處:(第6頁,offset為197,第4、5頁是預(yù)留給之后擴(kuò)展revmap的)

    demo=# select * from brin_revmap_data(get_raw_page('flights_bi_scheduled_time_idx',1)) limit 1;pages ---------(6,197) (1 row)

    這是摘要數(shù)據(jù)本身:

    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)

    等等。

    對于“包含”類,“值”字段將顯示如下內(nèi)容

    {(94.4005966186523,69.3110961914062),(77.6600036621,51.6693992614746) .. f .. f}

    第一個值是嵌入矩形,末尾第一個“f”字母表示缺少空元素,第二個f表示缺少不可合并的值。實際上,唯一不可合并的值是“IPv4”和“IPv6”地址(“inet”數(shù)據(jù)類型)。

    屬性

    我們之前已經(jīng)提供了查詢方法。

    以下是訪問方法的屬性:

    amname | name | pg_indexam_has_property --------+---------------+-------------------------brin | can_order | fbrin | can_unique | fbrin | can_multi_col | tbrin | can_exclude | f

    可以在多個列上創(chuàng)建索引。在這種情況下,它會為每一列收集自己的摘要統(tǒng)計信息,但它們會將每個范圍存儲在一起。當(dāng)然,如果一個相同大小的范圍適用于所有列,那么這個索引是有意義的。

    以下索引層的特性:

    name | pg_index_has_property ---------------+-----------------------clusterable | findex_scan | fbitmap_scan | tbackward_scan | f

    顯然,只支持位圖掃描。

    然而,集群的缺乏似乎令人困惑。從表面上看,由于BRIN索引對行的物理順序很敏感,因此能夠根據(jù)索引對數(shù)據(jù)進(jìn)行聚類是合乎邏輯的。但事實并非如此。我們只能創(chuàng)建一個“常規(guī)”索引(B樹或GiST,取決于數(shù)據(jù)類型)并根據(jù)它進(jìn)行聚類。順便問一下,你愿意對一個巨大的表進(jìn)行集群并同時考慮到獨占鎖、執(zhí)行時間和重建期間的磁盤空間消耗嗎?

    以下是列層面的特性:

    name | pg_index_column_has_property --------------------+------------------------------asc | fdesc | fnulls_first | fnulls_last | forderable | fdistance_orderable | freturnable | fsearch_array | fsearch_nulls | t

    唯一可用的屬性是操縱空值的能力。

    總結(jié)

    以上是生活随笔為你收集整理的PostgreSQL中的索引—9(BRIN)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。