從MySQL的MRR開始
開始之前,先從MySQL入手,看一下MySQL中的MRR機制原理,也就是Multi-Range Read,
MySQL中在按照非聚集索引的范圍查找且需要回表的情況下,比如select * from t where c2>100 and c2<200;c2為非聚集索引,
如果直接根據非聚集索引(二級索引)鍵中的聚集索引鍵去回表,會產生大量的隨機性IO讀取(圖1),
為了避免頻繁的回表造成的隨機IO,讀取完非聚集索引上符合條件的key值之后,對key值對應的聚集索引鍵(圖2的rowid)排序,然后根據排序后的聚集索引鍵順序地回表,從而避免大量的隨機性IO,
因為MySQL的Innodb表都是聚集表,那么圖2中的rowid排序后,是順序性的映射到聚集索引的page,從而避回表程序中的隨機性IO,
(圖1)
(圖2)
以上原理清楚后,繼續引申出來另外一個經典的問題:
MySQL中的Innodb總是聚集索引表,或者SqlServer中的聚集表,非聚集索引為什么要拿聚集索引鍵(而非物理地址)作為其行指標?
對于聚集表,表中資料的物理位置因為需要保證按聚集索引建有序,同時意味著其真正的物理的rowid可能會發生變化(比如聚集索引非線性寫入的時候,會導致葉分裂,頁分裂會導致原始記錄的物理位置變化),此時非聚集索引的行指標rowid也要做修改,這樣會導致聚集表中的資料發生物理位置變化的時候,非聚集索引也要做相應的變化,如果非聚集索參考對應的聚集索引鍵做指標的話,就不會發生該問題,
由以上兩個問題做鋪墊,來看看Postgresql中如何處理類似的問題,
Postgresql中的位圖掃描(bitmap scan)
如果遇到類似于上述的查詢(select * from t where c2>100 and c2<200;c2為非聚集索引的)情況下,查詢結果是一個范圍,那么Postgresql在回表的程序中,如何避免類似于上述圖1中的隨機性IO?
先弄清楚Postgresql的資料存盤特點,Postgresql表的資料都是以堆表(heap)的形式存盤的,因此Postgresql中不存在所謂的聚集索引,同時意味著其記錄在物理結構上可以是無序存盤,不會產生所謂的頁分裂(page split),
那么Postgresql中的行指標,這里稱作rowid,正常情況是不會因為新資料的寫入導致類似于MySQL或者sqlserver中的頁拆分(page split),
然后再說Postgresql的bitmap scan,bitmap scan的作用就是通過建立位圖的方式,將回表程序中對標訪問隨機性IO的轉換為順行性行為,從而減少查詢程序中IO的消耗,
先從一個非常簡單的demo入手,如下查詢,是一個典型的根據非聚集索引且需要回表的查詢,滿足以上的條件,
可以看到在對idx_c5上執行了一個Bitmap Index Scan,由于Bitmap Index Scan記錄的是符合條件的記錄所在的block,而非記錄的指標,通過類似于Oracle位圖索引的檢索模式進行資料的篩選,然后對這些位圖資訊指向的block排序后再進行回表(查詢),Bitmap Index Scan之后有一個Recheck Cond是因為決議block的時候需要Recheck ,
參考這里:The bitmap is one bit per heap page. The bitmap index scan sets the bits based on the heap page address that the index entry points to.
最后,bitmap scan之后,對表的訪問,總是通過bitmap Heap Scan完成,也就是執行計劃的第一行,
這里的bitmap scan與上文中提到的MySQL中的MRR的思路算是一致的,都是通過中間一個快取來避免隨機性的IO訪問,提升查詢效率,
與基于聚集索引的總是從B+樹的根節點通過二分法查找訪問相比,對于postgresql中的這種直接基于物理Id的訪問,從這一點上看,效率并不一定低,
bitmap scan的訪問優化是基于代價考慮的,對于類似的查詢,不總是一定走bitmap scan,如下,當訪問的資料范圍足夠小的時候,可能不會走bitmap scan,
另外,bitmap scan的優化可以是基于不同欄位或者不同篩選條件的,比如 where a>m and b>n(BitmapAndPath),亦或是where a>x or b>y(BitmapOrPath)這種訪問方式,都可以通過bitmap scan來優化實作,
如果了解Oracle中的bitmap類似索引,加上這里的一個圖例,應該比較容易理解bitmap生成機制
只不過筆者這里還有一個問題,因為postgresql中對行的update都是直接洗掉原始記錄,然后新寫入一條記錄來實作的,只要這條記錄的物理頁面不變,那么非聚集索引的行指標就不變,如果這個記錄的物理頁面發生了變話,是不是索引的指標也會發生變化?
相關引數
正常情況下,是否用到bitmap scan優化,postgresql 優化器是可以選擇出來一種最優的方式來執行的,但不保證總是可以生成最優化的執行計劃,可以通過禁用bitmap scan或者 seqscan來嘗試對比和調優,
任何優化都是一個系統工程,而不是一個單點工程,通過不同資源的消耗比例來提升整體性能,bitmap scan也并非完美無瑕,其優化理念是通過bitmap 的生成程序中增加記憶體和CPU欄位消耗來減少IO消耗,
如果是高性能存盤或者有充足的記憶體,并不一定總是發生物理IO,那么IO并不一定會是瓶頸,相反機械地去做bitmap的生成的話,反倒是一種浪費,
此時可以根據具體的IO能力,比如磁盤的隨機讀和順序讀代價引數,或者是禁用bitm scan等,來做整體上的優化方案,
參考
https://dba.stackexchange.com/questions/119386/understanding-bitmap-heap-scan-and-bitmap-index-scan
https://blog.csdn.net/weixin_33672400/article/details/89734245
https://www.cybertec-postgresql.com/en/postgresql-indexing-index-scan-vs-bitmap-scan-vs-sequential-scan-basics/
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/1162.html
標籤:PostgreSQL
上一篇:PostgreSQL中三種自增列sequence,serial,identity區別
下一篇:PostgreSQL筆記
