本文主要整理了數據庫經常使用的算法。算法
咱們雖然沒有必要從頭開始瞭解數據庫的底層算法是什麼,可是瞭解大概原理是必要的。數據庫
其實如今不少技術均可以從經典算法中找到原型,好比Hadoop其實就是合併算法演變過來了。數組
這樣說來算法至關於內功,若是能理解了這些算法,再學其餘的技術,就是一鞭一條痕 一摑一掌血服務器
在瞭解全部算法以前,須要先了解算法複雜度,這裏的算法複雜度主要指的是時間複雜度,是當數據量增長時運算如何增長的一種度量。至關於給算法一把標尺,這樣咱們才比如較那種算法更優。同時當數據量已經到了海量的級別,咱們必須儘量的扣性能,這樣才能保證整個架構的可用性。數據結構
這裏的複雜度主要指的算法的時間複雜度,是當數據量增長時運算如何增長。架構
下圖標識了幾種經常使用算法的複雜度,咱們能夠有個直觀的認識。函數
面臨海量數據時oop
正由於哈希表、均衡樹以及好的排序算法的時間複雜度是這個樣子,咱們纔會選用它。
性能
數據庫裏面最經常使用的排序算法莫過於合併排序,它主要用在對查詢優化、數據庫聯接上。優化
假設如今給了咱們兩個數量爲4的序列,要把他們按照從小到大的順序合併成一個8元素的序列,應該怎麼作。
能夠雙方都出一個元素過來比較,誰小則放到8元素序列中。好比下圖中:
第二輪:左邊的1已經放到下面去了,右邊的2尚未動。因此要比較的是3和2,固然2小 。
第三輪:比較3和4
重複
所有過程能夠看以下的Gif
總結一下:
仔細看上面的算法,是否是兩個序列合併以後就成了有序的呢。
不過要執行這種算法,有個必要條件是要合併的序列必須是有序的,這樣才能夠只比較當前元素完成排序。
可是對任意一個序列來講不多是徹底有序的。那麼此時就陷入了僵局。
那麼咱們可不能夠這樣想,單個元素確定是有序的吧,因此咱們若是把兩個1元素的序列合併,確定是能夠用這種算法的,這樣就獲得一個2元素的有序序列,若是此時還有一個2元素的有序序列,是否是能夠再合併。而後是4元素序列合併,接下來是8元素序列合併。
大概是下圖這個樣子
那要怎麼獲得這樣1元素的序列呢?固然是拆分。8元素拆分爲4,4拆分爲2,拆分爲1 。
好了,這個算法就完整了。
首先爲了得到1元素的序列 ,咱們須要把要排序的序列進行拆分,拆分之後再進行合併。
使用3個步驟將序列分爲一元序列。步驟數量的值是 log(N) ,好比如今是 N=8, log(N)=3
爲何呢?由於拆分的每一步都是把原序列長度除以2,因此要執行多少步就是能把原序列除2多少次,正好就是對數的定義嘛。
一樣,排序階段也有log(N)個步驟,理由與上面拆分階段的相同。
而每次個步驟,全部的元素都須要動一下才能移到下一個序列裏面,因此每一個步驟都須要執行N次運算。
也就是說**總體成本是 N*log(N) 次運算。**
完整過程以下:
若是熟悉Hadoop就知道,裏面的MapReduce其實就是這種思想,分而治之,把一個大的任務拆分紅若干小的任務,最後再各個擊破,合併便可。
能夠說MapReduce就是合併算法修改後的結果,它能夠運行在多處理、多服務器這種架構上罷了。
說到數據庫的數據結構,咱們最容易想到就是的相似於Excel那樣的數據表了。
以下圖
每一行表示一個主體,而每一列則是若干屬性或者說叫字段。
優勢是很是的直觀,缺點是太過簡單,當數據量太大的時候,查找不易。
那麼爲了優化查找,主要有兩種方法一是構建查找樹,一是Hash表。下面咱們分別介紹。
若是直接在數組或者陣列上進行查找,並且若是碰巧它又是有序的,天然好辦,可使用折半、插值等方法。可是實際上,大部分的數組不大多是有序的,因此須要在進行排序,須要消耗大量的資源和時間。
那麼有沒有辦法能夠插入和刪除效率還不錯,並且又比較高效的進行查找呢?
若是咱們一籌莫展的話,不妨從最簡單的狀況入手,若是如今只有{62},而後須要把88插入進來,就成了{62,88},若是如今要插入58,同時還保持有序,天然須要把88日後挪一下。能夠不挪嗎?
咱們知道樹這種結構,能夠方便的插入和刪除,而後引申出二叉樹結構了。
首先將62定爲根結點,88比62大,因此作爲62的右子樹,同理,58成爲左子樹。
下圖就是一棵二叉排序樹,只要對它進行中序遍歷就能夠得到一個有序的序列
好比此時咱們要查詢93,則能夠像下面同樣查詢。
咱們只要查到了93,就能夠知道它再哪一行,而後在這一行裏面去查找,範圍天然小了許多。
那麼查詢的成本呢?天然就是樹的層數,即$log(N)$
那麼設想這樣一個例子。
若是數據庫中的一張表含有一個country的字段,如今要找誰在China工做。若是是陣列的話,咱們須要將整張表都掃一下
可是若是把country字段中全部的元素創建一個二叉查找樹,則最多使用$log(N)$就能夠查找表明China的節點,而後經過這個節點就知道有哪些行須要考慮了。
這就是索引,索引就是用其餘的數據結構來表示某些列,能夠加速對此列的查找。
可是新的問題又來了,查找某個值用二叉查找樹挺好的,可是若是要查找兩個值之間的多個元素怎麼辦?使用二叉查找樹須要查找每一個樹的節點才能判斷是否在兩個值之間。
因而咱們引入了一種改進的樹——B+樹
其特徵爲:
能夠看出,多了一整行用來保存信息的,此時若是要找40~100之間的值,
只須要先找到40,而後遍歷後續節點便可。
好比找了M個後續節點,則須要$M+log(N)$次便可。
可是B+節點須要保持順序,若是在數據庫裏面增長或者刪除一行,則須要B+樹進行較大的變化,查入和刪除也是O(logN)複雜度的。
因此索引不能太多,它會減慢插入、更新、刪除行的操做。
前面說過使用Hash表進行查找,其時間複雜度只有O(1),應該是最快的查找方法了。
不論是普通的序列仍是順序表,咱們都須要拿要查找的值與序列中的元素進行比較,那麼可否只根據關鍵字key就能查找到對應的內存位置呢?
也就是存在這樣一種函數:
$$存儲位置 = f (關鍵字)$$
這就是所謂的散列技術,這個 $f$就是散列函數,又稱爲哈希函數。
採用散列數據將記錄存儲在一塊連續的空間裏面,這塊空間就叫散列表或者哈希表。
存儲的時候,經過散列函數能夠計算記錄的散列地址,並按照此地址進行存儲。
在查找的時候,也一樣使用此散列函數計算相應的地址進行查找。
也就是在哪存的就去哪找,那麼散列技術既是一種存儲技術,又是一種查找技術
散列技術最適合的場景是查找與給定值相等的記錄。由於不須要比較,效率大大提升。
可是若是遇到一個key對應多條記錄,就不適合用散列表了。好比使用關鍵字「男」去查找一個班的學生,明顯是不合適的。
一樣,散列表也不適合範圍查找,也就是查找40~100學號的同窗。
另外,咱們還會常常遇到兩個關鍵字使用散列函數卻獲得一樣的地址,這樣就衝突了,能夠能夠把這個key稱爲散列函數的同義詞,因此還須要設計方法來避免衝突。
上面說過一個好的哈希函數最關鍵的是讓散列地址均勻的分佈在存儲空間裏面,這樣能夠減小衝突,通常來講經常使用的散列函數都是將原來的數字按照某種規律變成另外一種數字的。
那麼若是$p$選得很差,衝突就會比較多了。
根據經驗,若是表長爲$m$,則$p$爲小於或等於$m$的最小質數或不包含小於20質因子的合數。
解決衝突咱們只介紹一種,就是鏈地址法
咱們能夠將全部關鍵字爲同義詞的記錄存儲在一個單鏈表中,這樣每一個鏈表就叫哈希桶
因此散列表只存儲全部同義詞子表的頭指針,這樣不管有多少衝突 ,只須要在當前位置給單鏈表增長結點。
那麼一個好的哈希函數應該讓哈希桶裏面的元素很是少纔對,這樣就能夠直接在表裏面查找到,時間複雜度爲O(1)