爲了讓查詢更加有效,Greenplum引入了索引,可是索引在一些應用場景上也會有訪問性能、過濾條件限制等問題,而位圖和基於位圖的訪問很好的解決了這一問題。今天咱們經過這篇文章提早來看一下Greenplum執行器的奧祕。node
首先來看一個普通的查詢數據結構
SELECT sid FROM student WHERE enroll_year = 2017 OR enroll_year = 2018 OR enroll_year = 2019;
執行上面查詢時,Greenplum最簡單的方式能夠採用對錶student順序掃描(Sequence Scan)。若是表student很大,而且查詢只須要讀取少許元組時,順序掃描須要將所有元組從數據文件讀出再進行過濾,這顯然不是一個經濟的方式。對此,Greenplum引入了索引,利用索引經過過濾條件能夠頗有效的定位到須要的元組,可是基於索引也會帶來自身的問題。 性能
第一個問題就是,若是結果元組比較多,但又沒有到所有數據那麼大的量級,經過索引訪問元組會致使隨機訪問磁盤,訪問性能上會差一個數量級。由於元組在磁盤上的存放順序跟索引鍵的排序有多是不一樣的,特別是對於存在多個索引的狀況下。Greenplum指望可以作到基於結果集的順序訪問磁盤。優化
另一個問題是,對於上面的查詢語句,若是過濾條件是多個過濾條件的組合好比析取,沒法直接利用索引進行查詢。一個可能的解決辦法是,能夠經過各個查詢條件分析到索引中查詢到結果,再將全部結果「合併」到一塊兒。經過最後「合併」後的結果進行查詢。spa
因此,Greenplum引入了位圖和基於位圖的訪問,位圖中的第n位對應Greenplum中的第n個元組,經過位圖中的1來表示對應元組命中,0表示跳過對應元組。若是將位圖與元組的位置進行映射,就涉及到Greenplum元組的尋址問題。另外,Greenplum從索引接口上也直接支持返回知足條件元組的位圖。設計
Greenplum中的表,從存儲格式上分爲Heap表和AO表。Heap表支持頻繁的元組更新和修改,結合磁盤讀寫的特色,爲了效率,以頁爲單位進行訪問。在Greenplum系統設計中,一個文件頁是以32K (BLCKSZ)字節爲大小,爲物理磁盤頁的整數倍。以32K字節爲大小,在聯機分析處理(OLAP)的場景下,能夠得到較好的文件IO吞吐量,特別是對應順序掃描的狀況。上一節中講到了表文件的分段存儲,從邏輯上來看,對於同一個表的數據文件,文件地址空間能夠認爲是一維的線性空間,而後按照文件頁大小32K進行切分。每一個文件大小限制最多爲32K(RELSEG_SIZE)個塊,而每一個頁大小是32K (BLCKSZ)字節,因此一個文件最大能夠爲1G(BLCKSZ * RELSEG_SIZE)字節。若是表的數據的很大,能夠採用多個文件連續存儲。每一個數據文件稱爲一個文件段(seg)。文件頁的邏輯序號Greenplum中稱爲塊號,塊號從第0開始,經過塊號 + 塊內偏移就能夠尋址尋址到任何字節。給出邏輯的一維地址(address)空間,能夠轉換成對應的塊號(block_number),也算出所對應的文件段號(seg_number)和對應段文件內偏移(seg_offset)。code
相互直接的轉換公式爲blog
block_number=address / BLCKSZ seg_number=address / (BLCKSZ*RELSEG_SIZE) seg_number=block_number / RELSEG_SIZE seg_offset=address % (BLCKSZ*RELSEG_SIZE)
因此,經過塊號 + 塊內偏移就可以尋址到第n個元組,Greenplum經過數據結構ItemPointerData來尋址元組。能夠看到結構的總大小爲48位。排序
Greenplum中假設每一個頁面可以存儲的最大元組數爲65536(64K),對於32K字節大小的頁面來講,該值已經足夠大,能夠經過下面公式換算位圖中的第n位bitmap_nth。索引
bitmap_nth=block_number * BLCKSZ + ip_posid
對於AO表的元組來講,也採用稱爲AOTupleId的48位結構來尋址元組,所存儲的內容會有不一樣,因爲篇幅限制,這裏不作熬述。
Greenplum中有種索引類型叫作位圖索引,其屬於索引的範疇,本文主要講解執行器,因此不會涵蓋位圖索引的內容。這裏只講述執行器中位圖的內存表示格式。
經過上面的映射關係,每一個元組存在與否的信息能夠經過一個比特位表示,經過下面的格式。
============================ | BitWord0 : | T0 | T1 | T2 | T3|...| T63| | ------------------------------------------------- | BitWord1: |T64|T64|...... |T127| | PAGE0 (WORDS_PER_PAGE個行) ------------------------------------------------- | ….. | ============================= | ============================ | BitWord0 : | T0 | T1 | T2 | T3|...| T63| | ------------------------------------------------- | BitWord1: |T64|T64|...... |T127| | PAGE1 ------------------------------------------------- | ….. | ============================= |
其中Tn表示第n個元組是否存在,經過0或1表表示。位圖會切分紅多個頁來存儲,每一個頁面內又會分紅多個行存儲,每行稱爲位圖的一個字(Word),用64位表示。當須要定位到第n個比特位時,先計算位圖中的頁號,而後在計算字號,最後是字內偏移號。
實現時,Greenplum考慮到了一種存儲上的優化,對於很大的表,文件很大的狀況下,文件頁的數目會比較多,對應位圖會佔有比較大的內存空間。另外,若是一個文件頁內幾乎全部元組都命中,沒有必要爲每一個元組都保留比特位。此時,只須要設置整個文件頁的比特位爲1,表示塊內有元組命中。不然,若是文件頁對應比特位爲0,則跳過掃描對應文件頁。這種存儲方式稱爲位圖的lossy存儲或有損存儲,由於lossy存儲丟失了每一個元組是否匹配的信息,可是減小了存儲空間。
當位圖中的位表示的是文件頁的而不是元組,掃描時須要對文件頁內的各個元組從新作一次檢查判斷其是否知足過濾條件,該檢查叫作recheck。
實現時,每一個位圖頁面的數據結構經過PagetableEntry實現。
typedef struct PagetableEntry { BlockNumber blockno; /* page number (hashtable key) */ bool ischunk; /* T = lossy storage, F = exact */ bool recheck; /* should the tuples be rechecked? */ tbm_bitmapword words[Max(WORDS_PER_PAGE, WORDS_PER_CHUNK)]; } PagetableEntry;
ischunk用來表示位圖頁中的存儲格式是否爲lossy方式,即位信息對應的是元組仍是文件頁的。
對於非lossy存儲格式,blockno記錄的是對應文件頁號。對於lossy存儲格式,每一個位圖頁能夠標記PAGES_PER_CHUNK個文件頁的信息,該大小定義爲BLCKSZ / 32。PagetableEntry記錄了連續PAGES_PER_CHUNK個文件頁的標記信息,其中blockno記錄了起始文件頁塊號。
基於位圖掃描元組的工做是經過執行器節點BitmapHeapScan來完成的。因爲歷史緣由,該節點不只支持基於Heap表的掃描,也支持基於AO表和AOCO列存表的掃描。其基本流程就是從前日後遍歷位圖,基於當前位圖頁的類型:
掃描時,根據是否recheck進行元組的從新過濾判斷返回知足條件的元組。
對於以下的複雜查詢
# explain select * from student where (sage = 10 OR sid = 20) AND (sage = 12 OR sid = 15); QUERY PLAN ---------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=5915.01..17857.54 rows=39185 width=15) -> Bitmap Heap Scan on student (cost=5915.01..17073.85 rows=13062 width=15) Recheck Cond: (((sage = 12) OR (sid = 15)) AND ((sage = 10) OR (sid = 20))) -> BitmapAnd (cost=5915.01..5915.01 rows=2097 width=0) -> BitmapOr (cost=2862.77..2862.77 rows=81370 width=0) -> Bitmap Index Scan on stu_sage_idx (cost=0.00..2838.99 ...) Index Cond: (sage = 12) -> Bitmap Index Scan on stu_sid_idx (cost=0.00..4.19 rows=1 width=0) Index Cond: (sid = 15) -> BitmapOr (cost=3051.99..3051.99 rows=86757 width=0) -> Bitmap Index Scan on stu_sage_idx (cost=0.00..3028.20 ...) Index Cond: (sage = 10) -> Bitmap Index Scan on stu_sid_idx (cost=0.00..4.19 rows=1 width=0) Index Cond: (sid = 20)
因爲sage列和sid列上都建立有索引,查詢優化器通過代價估算以後,採用先經過各自條件獲取過濾條件對應的位圖,而後位圖間作OR運算,最後兩個位圖間再作AND運算獲得最後的結果位圖。再基於最後的結果位圖進行Heap表掃描。
對於AND和OR操做,都是二元運算符,其輸入參數稱爲左位圖和右位圖。運算時,每次各取一個左右位圖頁進行操做返回一個結果位圖頁。對於每個左位圖頁和右位圖頁,存在下面可能四種狀況:
運算的邏輯能夠經過表格來表示
位圖在Greenplum中被普遍使用,本文只對Greenplum執行器中的位圖進行了簡單介紹,包括位圖的表示,基於位圖的元組掃描,和位圖上的運算AND和OR。對於位圖這種索引類型,不在本章介紹的範圍。經過本章的介紹,但願讀者可以對Greenplum執行器中的位圖有個初步認識,但願進一步瞭解代碼的讀者能夠參閱以下目錄和文件:
src/backend/access/bitmap
src/backend/executor/nodeBitmapAnd.c
src/backend/executor/nodeBitmapOr.c
src/backend/executor/nodeBitmapHeapscan.c