索引是一種讓你快速找到資料的資料結構

qing_yun發表於2022-11-04

人都是有慣性的,對於使用資料庫的人來說已經習慣於使用索引,大多數人都只知道我們可以用索引來提高資料訪問的效能。對於索引是如何實現這一點的,大家可能也清楚,透過只在葉結點中儲存索引資料的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索引獲得更好的效果。

create index idx_partial_status on t_order (status) where status=’001’;

這個索引中只有status=’001’的資料,因此索引十分小。訪問的效率也十分高。再複雜一些,我們可以建立類似這樣的PartialIndex。

create index idx_partial_status on t_order (status) where status in (’001’,’002’);

如果我們的where 條件是status in (’001’,’002’),那麼這個索引就能夠發揮作用了。

另外一種比較有趣的PG索引型別是覆蓋索引。在做Oracle資料庫 的時候,對於回表資料量較大的查詢,如果不回表訪問那麼可以大大提升效能。這種情況下Oracle有兩種方法來解決,一種是建立一個包含所有返回欄位的索引,使執行計劃變成INDEX ONLY SCAN,從而提升效能。不過如果要返回的欄位數量很多,那麼這個索引的冗餘部分就很多,甚至有時候我們只能使用索引組織表(IOT)來替代索引了。實際上可能在SQL中用於定位資料的出現在WHERE條件中的欄位數量並不多,大多數是為了避免回表而增加的額外欄位,是不需要排序的。因此PG資料庫中出現了一種被稱為覆蓋索引。覆蓋索引(Covering index)是PostgreSQL 11開始引入的一種新的索引。這是一種特殊的複合索引,允許索引中儲存附加的非索引欄位。比如:

select col1 from tab1 where col2=3;

在沒有覆蓋索引之前,我們需要建立一個(col2,col1)的複合索引,從而讓這條SQL使用Index Only Scan來提高執行效率,減少對錶的訪問。出現覆蓋索引後,可以建立一個(col2) include (col1)的索引。和傳統的複合索引不同的是,附加欄位不需要參與B-TREE的構建,讓索引的效率更高。

實際上在紛繁複雜的應用場景中,PG提供的索引種類可能還無法覆蓋一些特殊的場景。不過也不用怕,PG提供了一個十分簡單的方法,讓你擴充套件自己的索引型別,從而來解決你應用中很難解決的效能問題。只要你能夠想到,索引是一種讓你更快找到你所需要的資料的附加資料結構。你可以使用標準的,通用的B樹、點陣圖等結構,也可以使用只有你的應用能理解的資料結構來查詢到你所需要的資料,因此如果使用的是PG資料庫,那麼你很幸運,你可以自己去定義一種新的索引來適配你的應用。

也許我在寫這篇文章的時候,也有一些其他的資料庫也具備了這個能力,如果這樣,那就對了,索引本來就是這樣的,索引並不是你平時理解的那種死板的資料結構。也並不是你的應用必須去適合B樹索引,索引也可以去適應你的應用。希望我們的基於PG開原始碼開發的國產資料庫,千萬要保留這個介面,有時候它真的能救命。

來自 “ 白鱔的洞穴 ”, 原文作者:白鱔;原文連結:https://mp.weixin.qq.com/s/wbcn5jm3klWX7tYcsG-zvQ,如有侵權,請聯絡管理員刪除。

相關文章