二分尋找(Binary Search)需要注意的問題,以及在資料庫核心中的實現 | 深入MySQL核心

來源:互聯網
上載者:User

二分尋找(Binary Search)需要注意的問題,以及在資料庫核心中的實現 | 深入MySQL核心

二分尋找(Binary Search)需要注意的問題,以及在資料庫核心中的實現發表於 2013 年 4 月 1 日 由 hedengcheng

問題背景

 

今年的實習生招聘考試,我出了一道二分尋找(Binary Search)的題目。題目大意如下:

 

給定一個升序排列的自然數數組,數組中包含重複數字,例如:[1,2,2,3,4,4,4,5,6,7,7]。問題:給定任意自然數,對數組進行二分尋找,返回數組正確的位置,給出函數實現。註:連續相同的數字,返回第一個匹配位置還是最後一個匹配位置,由函數傳入參數決定。

 

我為什麼會出這道題目?

 

  • 二分尋找在資料庫核心實現中非常重要

    在資料庫的核心實現中,二分尋找是一個非常重要的邏輯,幾乎99%以上的SQL語句(所有索引上的範圍掃描/等值查詢/Unique查詢等),都會使用到二分尋找進行資料的定位。


    考慮一個資料庫表t1(a int primary key, b int),表上的b欄位有一個B+樹索引,表中記錄的b欄位取值,就是題目中的[1,2,2,3,4,4,4,5,6,7,7]序列。此時,給定以下的兩條查詢語句,就是使用到了不同的二分尋找邏輯:


    SQL1:   select * from t1 where b 4;

    SQL2: select * from t1 where b >= 4;


    針對SQL1,索引的二分尋找,就需要跳過所有的4,從最後一個4之後開始返回所有記錄;針對SQL2,二分尋找就需要定位到第一個4,然後順序讀取所有記錄。


    除此之外,針對資料庫中其他的查詢邏輯,二分尋找還需要附帶更多的功能,例如:


    SQL3: select * from t1 where b < 2;

    SQL4: select * from t1 where b <= 2;


    由於資料庫索引同時支援反向掃描,因此SQL3、SQL4的語句,都可以使用索引反向掃描。反向掃描時,SQL3需要定位到索引中的第一個2;而SQL4,則需要定位到索引的最後一個2,然後開始反向返回滿足查詢條件的索引記錄。

     


  • 二分尋找在程式設計中,是一個十分基礎並且易錯的功能

     

    第一個真正正確的二分尋找演算法,在第一個二分尋找實現之後的12年,才被發表出來。通過Google,輸入Binary Search或者是二分尋找關鍵字,有大量的相關的文章或者部落格討論此話題。

     

二分尋找實現,需要注意的問題

 

本文不準備詳細介紹一個正確的二分尋找應該是如何?的,畢竟現在網上有著大量的正確版本。接下來,根據批改試卷過程中發現的一些問題,做一些簡單的分析,希望對大家實現一個有效二分尋找演算法,甚至是一個資料庫內可用的二分尋找演算法,有所協助。

 

問題一:是否檢查參數的有效性

 

大量的試卷,在給出此問題的解決演算法時,直接拿著low,high參數開始進行計算,但是卻沒有檢查low/high參數。low/high是否相同,數組中是否存在記錄?low/high構成的區間是否有效?代碼的魯棒性不足。

 

在資料庫的二分尋找實現中,一般是對一個索引頁面進行二分尋找。索引頁面中有可能根本不存在使用者的記錄(索引頁面中的記錄全部被刪除,又沒有與兄弟頁面合并時),此時,low/high均為0,此時如果根據low/high計算出來的mid進行記錄的讀取,就存在邏輯錯誤。

 

問題二:二分尋找中值的計算

 

這是一個經典的話題,如何計算二分尋找中的中值?試卷中,大家一般給出了兩種計算方法:

 

演算法一: mid = (low + high) / 2

演算法二: mid = low + (high – low)/2

 

乍看起來,演算法一簡潔,演算法二提取之後,跟演算法一沒有什麼區別。但是實際上,區別是存在的。演算法一的做法,在極端情況下,(low + high)存在著溢出的風險,進而得到錯誤的mid結果,導致程式錯誤。而演算法二能夠保證計算出來的mid,一定大於low,小於high,不存在溢出的問題。

 

回到資料庫二分尋找,資料庫的一個索引頁面(大小一般是8k或者是16k),能夠儲存的索引記錄是有限的,因此肯定不會出現(low + high)溢出的風險。這也是為什麼InnoDB中的中值,採用的就是演算法一的實現。但是,作為一個嚴謹的程式設計人員,還是推薦使用演算法二,將任何潛在的風險,扼殺於搖籃之中。

 

問題三:遞迴實現二分尋找

 

超過一半的試卷,使用了遞迴調用的方式實現二分尋找。不能說遞迴實現有錯,而是在於實現效率問題。總所周知,遞迴調用存在著壓棧/出棧的開銷,其效率是比較低下的。而以資料庫這樣一個極端最佳化代碼效率,提供快速查詢響應的系統來說,效率是第一位的。不建議使用遞迴方式實現二分尋找,至少在資料庫核心實現中是不允許使用的。據我所知,所有的開來源資料庫系統,例如:InnoDB,PostgreSQL都未採用遞迴方式實現二分尋找。

 

問題四:如何尋找第一個/最後一個等值

 

回到題目,要求根據傳入的參數不同,返回第一個/最後一個等值項。在本文的背景部分,我也解釋了此問題對應的資料庫查詢(>,>=查詢需求是不同的)。在試卷中,超過80%的同學的答案都是先進行二分尋找,待定位到相同值之後,再根據傳入的flag(使用者需求:flag = 1,返回第一個等值項;flag = 0,返回最後一個等值項),進行順序遍曆,直至定位到滿足條件的項。

 

同樣,不能說這個實現是錯的,但是也存在著效能問題。效能效能效能,永遠是資料庫核心實現考慮的重點之一(相信也是所有應用程式的一個指標)。資料庫中,除了主鍵索引/Unique索引能夠保證索引值唯一之外,很多二級輔助索引都是存在相同索引值的,有時相同索引值的項會超過千項(考慮一個使用者的訂單,或者是購買記錄)。

 

假設一個索引頁面,儲存著400項記錄,均為相同索引值。此時,使用先二分尋找,後順序遍曆的演算法,二分尋找只能使用一次,順序遍曆199次,最終對比了200次。效率非常之低。當然,我也欣喜的看到另外一小部分同學的做法(我期待看到的演算法),用flag來糾正每次比較的最終結果。例如:比較相等(相等用0表示,大於為1,小於為-1),但是flag = 1,則返回糾正後的比較結果為1,需要移動二分尋找的high到mid,繼續二分(反之,若flag = 0,則返回糾正後的結果為-1,需要移動二分尋找的low到mid,繼續二分)。如此一來,等值仍舊可以進行二分尋找,最終的對比只需要9次,遠遠小於200次。

 

此問題,進一步引出了下一個問題,資料庫中如何?一個通用的,更為複雜的二分尋找演算法?

 

問題五:資料庫中的二分尋找實現舉例

 

資料庫中的二分尋找,更為複雜,需要實現一個通用型的二分尋找演算法,使用於各種不同的SQL查詢情境。

 

InnoDB針對不同的SQL語句,總結出四種不同的Search Mode,分別為:

 

#define    PAGE_CUR_G          1        >查詢

#define    PAGE_CUR_GE         2        >=,=查詢

#define    PAGE_CUR_L          3        <查詢

#define    PAGE_CUR_LE         4        <=查詢

 

然後根據這四種不同的Search Mode,在二分尋找碰到相同索引值時進行調整。例如:若Search Mode為PAGE_CUR_G或者是PAGE_CUR_LE,則移動low至mid,繼續進行二分尋找;若Search Mode為PAGE_CUR_GE或者是PAGE_CUR_L,則移動high至mid,繼續進行二分尋找。

 

我們的TNT引擎,採用了與InnoDB不同的方案,但是也實現了相同的功能。TNT引擎針對相同索引值的調整總結為,在此我就不做解釋了,大家可以嘗試著自己進行分析。

 

/* 操作符 includeKey     forward     compare result: 1    0        -1 */

=============================================================================

>=            1            1    |            1            -1        -1

=             1            1    |            1            -1        -1

>             0            1    |            1             1        -1

<             0            0    |            1            -1        -1

<=            1            0    |            1             1        -1

=============================================================================

 

總結

本文通過一個二分尋找的題目,以及同學們在解答題目中暴露出來的問題,分析了一個安全可靠高效的二分尋找,應該注意哪些問題。並簡要分析了資料庫核心實現中的二分尋找實現,希望對大家在以後設計二分尋找演算法時,有所協助。

此條目發表在 InnoDB, Programming, 資料庫, 資料庫核心分享, 資料庫研發 分類目錄,貼了 binary search, Database, 二分尋找 標籤。將固定連結加入收藏夾。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.