在MySQL中實現(xiàn)二分查找的詳細教程
給定一個升序排列的自然數(shù)數(shù)組,數(shù)組中包含重復數(shù)字,例如:[1,2,2,3,4,4,4,5,6,7,7]。問題:給定任意自然數(shù),對數(shù)組進行二分查找,返回數(shù)組正確的位置,給出函數(shù)實現(xiàn)。注:連續(xù)相同的數(shù)字,返回第一個匹配位置還是最后一個匹配位置,由函數(shù)傳入?yún)?shù)決定。
我為什么會出這道題目?
二分查找在數(shù)據(jù)庫內(nèi)核實現(xiàn)中非常重要
在數(shù)據(jù)庫的內(nèi)核實現(xiàn)中,二分查找是一個非常重要的邏輯,幾乎99%以上的SQL語句(所有索引上的范圍掃描/等值查詢/Unique查詢等),都會使用到二分查找進行數(shù)據(jù)的定位。
考慮一個數(shù)據(jù)庫表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,然后順序讀取所有記錄。
除此之外,針對數(shù)據(jù)庫中其他的查詢邏輯,二分查找還需要附帶更多的功能,例如:
SQL3:
select * from t1 where b < 2;
SQL4:
select * from t1 where b <= 2;
由于數(shù)據(jù)庫索引同時支持反向掃描,因此SQL3、SQL4的語句,都可以使用索引反向掃描。反向掃描時,SQL3需要定位到索引中的第一個2;而SQL4,則需要定位到索引的最后一個2,然后開始反向返回滿足查詢條件的索引記錄。
二分查找在程序設計中,是一個十分基礎并且易錯的功能
第一個真正正確的二分查找算法,在第一個二分查找實現(xiàn)之后的12年,才被發(fā)表出來。通過Google,輸入Binary Search或者是二分查找關鍵字,有大量的相關的文章或者博客討論此話題。
二分查找實現(xiàn),需要注意的問題
本文不準備詳細介紹一個正確的二分查找應該是如何實現(xiàn)的,畢竟現(xiàn)在網(wǎng)上有著大量的正確版本。接下來,根據(jù)批改試卷過程中發(fā)現(xiàn)的一些問題,做一些簡單的分析,希望對大家實現(xiàn)一個有效的二分查找算法,甚至是一個數(shù)據(jù)庫內(nèi)可用的二分查找算法,有所幫助。
問題一:是否檢查參數(shù)的有效性
大量的試卷,在給出此問題的解決算法時,直接拿著low,high參數(shù)開始進行計算,但是卻沒有檢查low/high參數(shù)。low/high是否相同,數(shù)組中是否存在記錄?low/high構成的區(qū)間是否有效?代碼的魯棒性不足。
在數(shù)據(jù)庫的二分查找實現(xiàn)中,一般是對一個索引頁面進行二分查找。索引頁面中有可能根本不存在用戶的記錄(索引頁面中的記錄全部被刪除,又沒有與兄弟頁面合并時),此時,low/high均為0,此時如果根據(jù)low/high計算出來的mid進行記錄的讀取,就存在邏輯錯誤。
問題二:二分查找中值的計算
這是一個經(jīng)典的話題,如何計算二分查找中的中值?試卷中,大家一般給出了兩種計算方法:
算法一: mid = (low + high) / 2
算法二: mid = low + (high – low)/2
乍看起來,算法一簡潔,算法二提取之后,跟算法一沒有什么區(qū)別。但是實際上,區(qū)別是存在的。算法一的做法,在極端情況下,(low + high)存在著溢出的風險,進而得到錯誤的mid結果,導致程序錯誤。而算法二能夠保證計算出來的mid,一定大于low,小于high,不存在溢出的問題。
回到數(shù)據(jù)庫二分查找,數(shù)據(jù)庫的一個索引頁面(大小一般是8k或者是16k),能夠存儲的索引記錄是有限的,因此肯定不會出現(xiàn)(low + high)溢出的風險。這也是為什么InnoDB中的中值,采用的就是算法一的實現(xiàn)。但是,作為一個嚴謹?shù)某绦蛟O計人員,還是推薦使用算法二,將任何潛在的風險,扼殺于搖籃之中。
問題三:遞歸實現(xiàn)二分查找
超過一半的試卷,使用了遞歸調(diào)用的方式實現(xiàn)二分查找。不能說遞歸實現(xiàn)有錯,而是在于實現(xiàn)效率問題。總所周知,遞歸調(diào)用存在著壓棧/出棧的開銷,其效率是比較低下的。而以數(shù)據(jù)庫這樣一個極端優(yōu)化代碼效率,提供快速查詢響應的系統(tǒng)來說,效率是第一位的。不建議使用遞歸方式實現(xiàn)二分查找,至少在數(shù)據(jù)庫內(nèi)核實現(xiàn)中是不允許使用的。據(jù)我所知,所有的開源數(shù)據(jù)庫系統(tǒng),例如:InnoDB,PostgreSQL都未采用遞歸方式實現(xiàn)二分查找。
問題四:如何查找第一個/最后一個等值
回到題目,要求根據(jù)傳入的參數(shù)不同,返回第一個/最后一個等值項。在本文的背景部分,我也解釋了此問題對應的數(shù)據(jù)庫查詢(>,>=查詢需求是不同的)。在試卷中,超過80%的同學的答案都是先進行二分查找,待定位到相同值之后,再根據(jù)傳入的flag(用戶需求:flag = 1,返回第一個等值項;flag = 0,返回最后一個等值項),進行順序遍歷,直至定位到滿足條件的項。
同樣,不能說這個實現(xiàn)是錯的,但是也存在著性能問題。性能性能性能,永遠是數(shù)據(jù)庫內(nèi)核實現(xiàn)考慮的重點之一(相信也是所有應用程序的一個指標)。數(shù)據(jù)庫中,除了主鍵索引/Unique索引能夠保證鍵值唯一之外,很多二級輔助索引都是存在相同鍵值的,有時相同鍵值的項會超過千項(考慮一個用戶的訂單,或者是購買記錄)。
假設一個索引頁面,保存著400項記錄,均為相同鍵值。此時,使用先二分查找,后順序遍歷的算法,二分查找只能使用一次,順序遍歷199次,最終對比了200次。效率非常之低。當然,我也欣喜的看到另外一小部分同學的做法(我期待看到的算法),用flag來糾正每次比較的最終結果。例如:比較相等(相等用0表示,大于為1,小于為-1),但是flag = 1,則返回糾正后的比較結果為1,需要移動二分查找的high到mid,繼續(xù)二分(反之,若flag = 0,則返回糾正后的結果為-1,需要移動二分查找的low到mid,繼續(xù)二分)。如此一來,等值仍舊可以進行二分查找,最終的對比只需要9次,遠遠小于200次。
此問題,進一步引出了下一個問題,數(shù)據(jù)庫中如何實現(xiàn)一個通用的,更為復雜的二分查找算法?
問題五:數(shù)據(jù)庫中的二分查找實現(xiàn)舉例
數(shù)據(jù)庫中的二分查找,更為復雜,需要實現(xiàn)一個通用型的二分查找算法,使用于各種不同的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 <=查詢
然后根據(jù)這四種不同的Search Mode,在二分查找碰到相同鍵值時進行調(diào)整。例如:若Search Mode為PAGE_CUR_G或者是PAGE_CUR_LE,則移動low至mid,繼續(xù)進行二分查找;若Search Mode為PAGE_CUR_GE或者是PAGE_CUR_L,則移動high至mid,繼續(xù)進行二分查找。
我們的TNT引擎,采用了與InnoDB不同的方案,但是也實現(xiàn)了相同的功能。TNT引擎針對相同鍵值的調(diào)整總結為下圖,在此我就不做解釋了,大家可以嘗試著自己進行分析。
/* 操作符 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
=============================================================================
相關文章
Linux下MySQL安裝配置 MySQL配置參數(shù)詳解
Linux下MySQL安裝配置 MySQL配置參數(shù)詳解,在linux下配置mysql的朋友可以參考下。2011-07-07MySQL按常規(guī)排序、自定義排序和按中文拼音字母排序的方法
MySQL常規(guī)排序、自定義排序和按中文拼音字母排序,在實際的SQL編寫時,我們有時候需要對條件集合進行排序。下面給出3種比較常用的排序方式,一起看看吧2017-04-04基于sqlalchemy對mysql實現(xiàn)增刪改查操作
這篇文章主要介紹了基于sqlalchemy對mysql實現(xiàn)增刪改查操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06mysql中循環(huán)截取用戶信息并插入到目標表對應的字段中
將各個用戶對應的屬性插入到目標表對應的字段中,last_update為數(shù)據(jù)更新日期2014-08-08