索引是一種讓你快速找到數據的數據結構
?人都是有慣性的,對于使用數據庫的人來說已經習慣于使用索引,大多數人都只知道我們可以用索引來提高數據訪問的性能。對于索引是如何實現這一點的,大家可能也清楚,通過只在葉結點中存儲索引數據的B+TREE來快速定位到數據所在的位置,再從表中獲得數據就可以實現比全表掃描更快的獲得數據的目的了。
不過隨著慣性,我們一直都在把我們的業務模型與B樹去做融合,盡可能讓我們的應用訪問數據的模式更符合B樹的結構,從而獲得更好的性能。比如說控制不會在索引中出現,那么我們給創建一個(col,1)這樣的索引,讓索引中也能夠包含col的空值記錄。比如說我們的索引字段的獨立值數很少的時候,會發現使用索引可能還不如全表掃描快,使用位圖索引又容易出現并發寫入時鎖放大的性能問題。如果我們只訪問幾個占表的記錄數中較少的值的時候,我們發現可以用B樹索引來提升性能。只不過我們創建的索引包含了對所有數據的索引值對于應用來說是沒有任何用途的。
實際上在使用索引的時候,我們已經忘記了使用索引的目的就是為了更快速的找到數據,索引并不是只能是B樹或者位圖,索引是一種輔助性的數據結構,它其實是可以被定義成任何樣式的。比如僅僅是為了解決你的某條SQL中幾張表的復雜關聯關系,或者僅僅為了某個應用所需要的快速查找數據的需求。你可以自己設計一種最符合應用特點的索引結構,來實現對此類應用的加速。
實際上有一類對表關聯查詢特別有效的索引,這種索引出現了幾十年了,可能我們還從來沒有使用過,那就是連接位圖索引BMJ。這種索引在OLAP系統中可能用的更多一些,在OLTP系統中,因為會影響DML的性能而很少使用。不過如果你的數據是寫入后較少改動的,并且并發寫入不存在明顯瓶頸的時候,BMJ在OLTP中使用也是安全的。BMJ是一種專門用于表連接的索引,其性能高于一般的HASH JOIN或者NL。
如果你在使用PostgreSQL數據庫,那么你會對索引設計感到既興奮又迷茫。PG數據庫的索引類型太豐富了。哪怕我們排除一些用于全文檢索,JSON的索引類。我們也能發現很多有趣的索引類型。
比如說我們上面的這個例子,每次我們只是從上億條數據中找出幾百條特殊的數據,那么在PG里就可以使用部分索引(Partial Index),這種索引我們可以看作是一種特殊的函數索引,其存儲結構也是B-TREE的。部分索引也稱為過濾索引,它只覆蓋表數據的一個子集,是一個帶有 WHERE 子句的索引。部分索引有助于加快查詢速度,同時減少索引的大小,這些索引需要更少的存儲空間,它們更易于維護,掃描速度更快。比如在一張表上,STATUS=001的數據是我們要SELECT出來進行處理的,處理后STATUS就變成了002,因此這張表上的STATUS字段值域是傾斜的,001的記錄可能只有幾百條,而002的記錄有上千萬條。在PostgreSQL中,我們可以通過Partial索引獲得更好的效果。
這個索引中只有status=’001’的數據,因此索引十分小。訪問的效率也十分高。再復雜一些,我們可以創建類似這樣的PartialIndex。
如果我們的where 條件是status in (’001’,’002’),那么這個索引就能夠發揮作用了。
另外一種比較有趣的PG索引類型是覆蓋索引。在做Oracle數據庫 的時候,對于回表數據量較大的查詢,如果不回表訪問那么可以大大提升性能。這種情況下Oracle有兩種方法來解決,一種是創建一個包含所有返回字段的索引,使執行計劃變成INDEX ONLY SCAN,從而提升性能。不過如果要返回的字段數量很多,那么這個索引的冗余部分就很多,甚至有時候我們只能使用索引組織表(IOT)來替代索引了。實際上可能在SQL中用于定位數據的出現在WHERE條件中的字段數量并不多,大多數是為了避免回表而增加的額外字段,是不需要排序的。因此PG數據庫中出現了一種被稱為覆蓋索引。覆蓋索引(Covering index)是PostgreSQL 11開始引入的一種新的索引。這是一種特殊的復合索引,允許索引中存儲附加的非索引字段。比如:
在沒有覆蓋索引之前,我們需要創建一個(col2,col1)的復合索引,從而讓這條SQL使用Index Only Scan來提高執行效率,減少對表的訪問。出現覆蓋索引后,可以創建一個(col2) include (col1)的索引。和傳統的復合索引不同的是,附加字段不需要參與B-TREE的構建,讓索引的效率更高。
實際上在紛繁復雜的應用場景中,PG提供的索引種類可能還無法覆蓋一些特殊的場景。不過也不用怕,PG提供了一個十分簡單的方法,讓你擴展自己的索引類型,從而來解決你應用中很難解決的性能問題。只要你能夠想到,索引是一種讓你更快找到你所需要的數據的附加數據結構。你可以使用標準的,通用的B樹、位圖等結構,也可以使用只有你的應用能理解的數據結構來查找到你所需要的數據,因此如果使用的是PG數據庫,那么你很幸運,你可以自己去定義一種新的索引來適配你的應用。
也許我在寫這篇文章的時候,也有一些其他的數據庫也具備了這個能力,如果這樣,那就對了,索引本來就是這樣的,索引并不是你平時理解的那種死板的數據結構。也并不是你的應用必須去適合B樹索引,索引也可以去適應你的應用。希望我們的基于PG開源代碼開發的國產數據庫,千萬要保留這個接口,有時候它真的能救命。?