在下面這個表t中,如果我執行 select * from t where k between 3 and 5,需要執行幾次樹的搜尋操作,會掃瞄多少行?
下面是這個表的初始化語句。
在k索引樹上找到k=3的記錄,取得 id = 300;
再到id索引樹查到id=300對應的r3;
在k索引樹取下乙個值k=5,取得id=500;
再回到id索引樹查到id=500對應的r4;
在k索引樹取下乙個值k=6,不滿足條件,迴圈結束。
在這個過程中,回到主鍵索引樹搜尋的過程,我們稱為回表。可以看到,這個查詢過程讀了k索引樹的3條記錄(步驟1、3和5),回表了兩次(步驟2和4)。
在這個例子中,由於查詢結果所需要的資料只在主鍵索引上有,所以不得不回表。那麼,有沒有可能經過索引優化,避免回表過程呢?
如果執行的語句是select id from t where k between 3 and 5,這時只需要查id的值,而id的值已經在k索引樹上了,因此可以直接提供查詢結果,不需要回表。也就是說,在這個查詢裡面,索引k已經「覆蓋了」我們的查詢需求,我們稱為覆蓋索引。
由於覆蓋索引可以減少樹的搜尋次數,顯著提公升查詢效能,所以使用覆蓋索引是乙個常用的效能優化手段。
需要注意的是,在引擎內部使用覆蓋索引在索引k上其實讀了三個記錄,r3~r5(對應的索引k上的記錄項),但是對於mysql的server層來說,它就是找引擎拿到了兩條記錄,因此mysql認為掃瞄行數是2。
基於上面覆蓋索引的說明,我們來討論乙個問題:在乙個市民資訊表上,是否有必要將身份證號和名字建立聯合索引?
假設這個市民表的定義是這樣的:
create table `tuser` ( `id` int(11) not null, `id_card` varchar(32) default null, `name` varchar(32) default null, `age` int(11) default null, `ismale` tinyint(1) default null, primary key (`id`), key `id_card` (`id_card`), key `name_age` (`name`,`age`)) engine=innodb
我們知道,身份證號是市民的唯一標識。也就是說,如果有根據身份證號查詢市民資訊的需求,我們只要在身份證號欄位上建立索引就夠了。而再建立乙個(身份證號、姓名)的聯合索引,是不是浪費空間?
如果現在有乙個高頻請求,要根據市民的身份證號查詢他的姓名,這個聯合索引就有意義了。它可以在這個高頻請求上用到覆蓋索引,不再需要回表查整行記錄,減少語句的執行時間。
當然,索引欄位的維護總是有代價的。因此,在建立冗餘索引來支援覆蓋索引時就需要權衡考慮了。這正是業務dba,或者稱為業務資料架構師的工作。
看到這裡你一定有乙個疑問,如果為每一種查詢都設計乙個索引,索引是不是太多了。如果我現在要按照市民的身份證號去查他的家庭位址呢?雖然這個查詢需求在業務**現的概率不高,但總不能讓它走全表掃瞄吧?反過來說,單獨為乙個不頻繁的請求建立乙個(身份證號,位址)的索引又感覺有點浪費。應該怎麼做呢?
這裡,我先和你說結論吧。b+樹這種索引結構,可以利用索引的「最左字首」,來定位記錄。
為了直觀地說明這個概念,我們用(name,age)這個聯合索引來分析。
可以看到,索引項是按照索引定義裡面出現的字段順序排序的。
當你的邏輯需求是查到所有名字是「張三」的人時,可以快速定位到id4,然後向後遍歷得到所有需要的結果。
如果你要查的是所有名字第乙個字是「張」的人,你的sql語句的條件是"where name like 『張%』"。這時,你也能夠用上這個索引,查詢到第乙個符合條件的記錄是id3,然後向後遍歷,直到不滿足條件為止。
可以看到,不只是索引的全部定義,只要滿足最左字首,就可以利用索引來加速檢索。這個最左字首可以是聯合索引的最左n個字段,也可以是字串索引的最左m個字元。
基於上面對最左字首索引的說明,我們來討論乙個問題:在建立聯合索引的時候,如何安排索引內的字段順序。
這裡我們的評估標準是,索引的復用能力。因為可以支援最左字首,所以當已經有了(a,b)這個聯合索引後,一般就不需要單獨在a上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護乙個索引,那麼這個順序往往就是需要優先考慮採用的。
所以現在你知道了,這段開頭的問題裡,我們要為高頻請求建立(身份證號,姓名)這個聯合索引,並用這個索引支援「根據身份證號查詢位址」的需求。
那麼,如果既有聯合查詢,又有基於a、b各自的查詢呢?查詢條件裡面只有b的語句,是無法使用(a,b)這個聯合索引的,這時候你不得不維護另外乙個索引,也就是說你需要同時維護(a,b)、(b) 這兩個索引。
這時候,我們要考慮的原則就是空間了。比如上面這個市民表的情況,name欄位是比age欄位大的 ,那我就建議你建立乙個(name,age)的聯合索引和乙個(age)的單字段索引。
上一段我們說到滿足最左字首原則的時候,最左字首可以用於在索引中定位記錄。這時,你可能要問,那些不符合最左字首的部分,會怎麼樣呢?
我們還是以市民表的聯合索引(name, age)為例。如果現在有乙個需求:檢索出表中「名字第乙個字是張,而且年齡是10歲的所有男孩」。那麼,sql語句是這麼寫的:
mysql> select * from tuser where name like '張%' and age=10 and ismale=1;
你已經知道了字首索引規則,所以這個語句在搜尋索引樹的時候,只能用 「張」,找到第乙個滿足條件的記錄id3。當然,這還不錯,總比全表掃瞄要好。
然後呢?
當然是判斷其他條件是否滿足。
在mysql 5.6之前,只能從id3開始乙個個回表。到主鍵索引上找出資料行,再對比字段值。
而mysql 5.6 引入的索引下推優化(index condition pushdown), 可以在索引遍歷過程中,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數。
圖3和圖4,是這兩個過程的執行流程圖。
在圖3和4這兩個圖裡面,每乙個虛線箭頭表示回表一次。
圖3中,在(name,age)索引裡面我特意去掉了age的值,這個過程innodb並不會去看age的值,只是按順序把「name第乙個字是』張』」的記錄一條條取出來回表。因此,需要回表4次。
圖4跟圖3的區別是,innodb在(name,age)索引內部就判斷了age是否等於10,對於不等於10的記錄,直接判斷並跳過。在我們的這個例子中,只需要對id4、id5這兩條記錄回表取資料判斷,就只需要回表2次。
今天這篇文章,我和你繼續討論了資料庫索引的概念,包括了覆蓋索引、字首索引、索引下推。你可以看到,在滿足語句需求的情況下, 盡量少地訪問資源是資料庫設計的重要原則之一。我們在使用資料庫的時候,尤其是在設計表結構時,也要以減少資源消耗作為目標。
上期的問題是,通過兩個alter 語句重建索引k,以及通過兩個alter語句重建主鍵索引是否合理。
重建索引k的做法是合理的,可以達到省空間的目的。但是,重建主鍵的過程不合理。不論是刪除主鍵還是建立主鍵,都會將整個表重建。所以連著執行這兩個語句的話,第乙個語句就白做了。這兩個語句,你可以用這個語句代替 : alter table t engine=innodb。
05 深入淺出索引 下
前提建表語句 create table t id intprimary key,k int notnull default0,s varchar 16 not null default index k k engine innodb insert into t values 100,1,aa 200...
深入淺出理解索引
一 深入淺出理解索引結構 實際上,您可以把索引理解為一種特殊的目錄。sql server提供了兩種索引 聚集索引 clustered index,也稱聚類索引 簇集索引 和非聚集索引 nonclustered index,也稱非聚類索引 非簇集索引 下面,我們舉例來說明一下聚集索引和非聚集索引的區別...
深入淺出理解索引
一 深入淺出理解索引結構 實際上,您可以把索引理解為一種特殊的目錄。sql server提供了兩種索引 聚集索引 clustered index,也稱聚類索引 簇集索引 和非聚集索引 nonclustered index,也稱非聚類索引 非簇集索引 下面,我們舉例來說明一下聚集索引和非聚集索引的區別...