國內較多的互聯網公司都是採用MySQL做爲數據庫系統,隨着業務的發展,不免會碰到須要新建索引來優化某些SQL執行性能的狀況。在MySQL實現online create index以前,新建索引意味着業務要中止寫入,這是很是影響用戶使用體驗的,爲此,MySQL引入了online create index,極大地減小了業務停寫的時間,使得新建索引期間業務可以持續正常的工做。本文主要是對其實現原理的總結以及關鍵步驟的解釋說明。html
在MySQL中表格至少須要設置一個主鍵,若是用戶未指定主鍵的話,內部會自動生成一個。對於帶主鍵的表格,MySQL會以彙集索引的方式實現,即表格的數據都是完整的存儲在彙集索引上的。對於主鍵的變動,至關於對彙集索引進行變動,這個過程目前MySQL仍是以停寫的方式實現的,本文主要討論的是新建二級索引的實現,爲了方便描述,以一個例子來講明本文要討論的場景。mysql
create table t1(
c1 int primary key,
c2 int,
c3 int,
);
複製代碼
剛開始業務中的SQL都是以主鍵c1來作查詢的,後來隨着業務的發展,可能出現了以c2作查詢的SQL,此時,爲了優化此類SQL的執行性能,須要在c2列上構建索引,即git
create index index_c2 on t1(c2);
複製代碼
MySQL online create index主要分爲兩個階段,第一階段爲從主表讀取索引列並排序生成索引表的數據,稱爲基線數據;第二階段爲把新建索引階段索引表的增量數據更新到第一階段的基線數據上。具體來看,主要過程以下。github
接下來將略過不過重要的步驟1和步驟5,主要描述步驟2-4的詳細實現。算法
在執行create index語句以後,MySQL會先等待以前開啓的事務先結束後,再真正開始索引的構建工做,這麼作的緣由是在執行create index
以前開啓的事務可能已經執行過某些更新SQL語句,這些SQL語句沒有生成新建索引表的增量數據(Row Log),若是不等待這部分事務結束,可能會出現基線數據中沒有此部分數據,且Row Log中也沒有此部分數據,最終該部分數據在索引表中不存在。sql
MySQL的等事務結束是經過MDL(Meta Data Lock)實現的,MDL會按序喚醒鎖等待者,這樣就能保證create index以前開啓的事務必定執行完成了。數據庫
實際測試中,能夠觀察到當create index以前的事務一直沒有結束時,create index語句會一直卡在thd->mdl_context.upgrade_shared_lock
(sql_table.cc:7381)上。微信
索引構建的第一階段的工做是根據主表的數據,來構建索引表的數據。此過程總共有兩個步驟,第一是讀取主表中所須要的索引列數據;第二是將數據按照索引列排序。性能
其中讀取主表數據和普通的全表掃描區別不大,而將數據按照索引列排序則是一個外部排序的過程。MySQL對外部排序實現較爲簡單,僅爲最普通的單線程兩路歸併算法,優勢是實現簡單,佔用內存資源少,缺點是性能較差。測試
通常地,對於數據量較大的表格,構建索引的時間較長,一般是小時級別的,這期間每每會有新事務的提交,其中就可能包含對新建索引表的修改。所以,在索引基線數據構建好以後,還須要把構建期間的增量數據更新到索引表中,那麼問題來了,在更新增量數據到索引表中會不斷的有新事務修改數據,這樣什麼時候才能保證全部的修改都更新到索引表上呢?答案是加鎖,粗暴一點的加鎖方式是在整個增量數據更新到索引表期間停寫,完成以後,再放開寫入。可是,由於索引構建時間長,增量數據的數據量通常也較大,若是更新整個增量數據到索引表期間都停寫的話,會較大地影響用戶使用體驗。所以,MySQL對加鎖過程作了優化。
首先Row Log會被拆分爲多個較小的Block,事務的更新會把數據寫入到最後一個Block中,所以,普通的DML更新的時候會對最後一個Block加鎖。一樣的,在更新每一個Block到索引表的時候,會先加鎖,若是當前Block不是最後一個Block時,會把鎖釋放,若是是最後一個Block,則保持加鎖狀態,直到更新結束。所以,在更新Row Log到索引表期間,加鎖的時間比較短,僅在最後一個Block更新到索引表時會持有鎖一段時間。
MySQL online create index的總體思路分爲兩步構建基線以及更新增量,構建基線時採用的歸併算法比較簡單,資源佔用少,但性能會比較差;在更新增量時,採用將增量切分紅更小的塊,來減小停寫的時間,是比較通用的方法。
PS: 本博客更新會在第一時間推送到微信公衆號,歡迎你們關注。