原文連接:http://hedengcheng.com/?p=525php
今天,看到Twitter的DBA團隊發佈了其最新的MySQL分支:Changes in Twitter MySQL 5.5.28.t9,此分支最重要的一個改進,就是修復了MySQL 的Bug #67718:InnoDB drastically under-fills pages in certain conditions。關於此Bug的詳細描述,以及如何重現此問題,能夠閱讀以上的Bug連接,如下簡單描述下此Bug對應的問題:html
InnoDB的索引分裂策略,在特定的狀況下,索引頁面的分裂存在問題,致使每一個分裂出來的頁面,僅僅存儲一條記錄,頁面的空間利用率極低。
mysql
此Bug引發了個人興趣,所以準備跟你們簡單聊聊B+樹索引的結構、B+樹的分裂、B+樹分裂操做的優化、Bug #67718的成因,以及我的對如何修復此Bug的一些建議等。sql
傳統關係型數據庫(Oracle/MySQL/PostgreSQL…),其主要的索引結構,使用的都是B+樹。更有甚者,InnoDB引擎的表數據,整個都是以B+樹的組織形式存放的。下圖,是一個經典的B+樹組織結構圖(2層B+樹,每一個頁面的扇出爲4):數據庫
注意:優化
在上圖B+樹的基礎上,繼續插入記錄6,7,B+樹結構會產生如下的一系列變化:3d
插入記錄6,新的B+樹結構以下:htm
插入記錄7,因爲葉頁面中只能存放4條記錄,插入記錄7,致使葉頁面分裂,產生一個新的葉頁面。blog
傳統B+樹頁面分裂操做分析:索引
疑問:
傳統50%分裂的策略,有不足之處,如何優化?接着往下看。
因爲傳統50%分裂的策略,有不足之處,所以,目前全部的關係型數據庫,包括Oracle/InnoDB/PostgreSQL,以及本人之前參與研發的Oscar數據庫,目前正在研發的NTSE、TNT存儲引擎,都針對B+樹索引的遞增/遞減插入進行了優化。通過優化,以上的B+樹索引,在記錄6插入完畢,記錄7插入引發分裂以後,新的B+樹結構以下圖所示:
對比上下兩個插入記錄7以後,B+樹索引的結構圖,能夠發現兩者有不少的不一樣之處:
所以,此優化分裂策略,僅僅是針對遞增遞減插入有效,針對隨機插入,就失去了優化的意義,反而帶來了更高的分裂機率。
在InnoDB的實現中,爲每一個索引頁面維護了一個上次插入的位置,以及上次的插入是遞增/遞減的標識。根據這些信息,InnoDB可以判斷出新插入到頁面中的記錄,是否仍舊知足遞增/遞減的約束,若知足約束,則採用優化後的分裂策略;若不知足約束,則退回到50%的分裂策略。
可是,InnoDB的實現,有不足之處,會致使下面提到的一個Bug。
在Bug#67718中提到,在特定的插入狀況下,InnoDB的索引頁面利用率極低,這是因爲InnoDB不正確的使用優化分裂策略致使的。
考慮如下的一個B+樹,已有的用戶數據是1,2,3,4,5,6,100,而且在插入記錄100以後,引發索引頁面分裂,記錄100在分裂後被插入到新的頁面:
因爲插入100可以知足遞增的判斷條件,所以採用了優化分裂策略,分裂不移動數據,新紀錄100插入到新頁面之中,原有頁面的最後插入位置仍舊是6號記錄不變,原有頁面仍舊保持遞增的插入標識不變。
此時,考慮連續插入9,8,7這幾條記錄,會獲得什麼樣的B+樹?此時,全局遞增插入變爲全局遞減插入。
插入記錄9後的B+樹結構:
因爲InnoDB的B+樹,上層節點保存的是下層頁面中的最小值(Low Key),所以記錄9仍舊會插入到【3,4,5,6】頁面,此時頁面已滿,須要分裂。並且判斷出記錄9仍舊知足頁面中的遞增判斷條件(Last_Insert_Pos = 6,9插入到6以後,而且原來是遞增插入的)。所以,採用優化的分裂策略,產生新的頁面插入記錄9,原有頁面記錄保持不變。
插入記錄8後的B+樹結構:
插入記錄7,也同樣。採用優化的分裂策略,記錄7獨佔一個頁面。
分析:
在本人作Oscar數據庫的索引分裂優化時,當時也一樣碰到了此問題。當時的解決方案是:每次分裂,若插入的記錄是頁面中的最後一條記錄,則至少將此記錄前一條記錄分裂到新頁面之中。採用此策略,針對100,9,8這一個系列的插入,會產生如下的系列B+樹:
插入100,9,8後的B+樹:
插入100時,移動原有頁面最後一條記錄到新的頁面(將6移動到新頁面),此時新頁面中的記錄爲【6,100】。接下來插入9,8,都會插入到新的頁面之中,不會產生分裂操做,空間利用率提升,減小了索引頁面分裂,解決了Bug#67718的問題。
固然,確定還有更優的策略,歡迎感興趣的朋友們一塊兒討論!