關係型數據庫工做原理-數據結構(翻譯自Coding-Geek文章)

關係型數據庫工做原理-數據結構(翻譯自Coding-Geek文章)

 
 
 

本文翻譯自Coding-Geek文章:《 How does a relational database work》。 
原文連接:http://coding-geek.com/how-databases-work/#Buffer-Replacement_strategies 
本文翻譯了以下章節: node

這裏寫圖片描述

 

1、Array、Tree and Hash table

經過前面的章節, 咱們已經理解了時間複雜和歸併排序的概念,接下來我要介紹三種數據結構。這三種數據結構很是重要,它們是現代數據庫系統的基石。我也會講一講數據庫索引的概念。算法

2、Array-數組

二位數組是一種最簡單的數據結構,一張數據庫表就能夠當作是一個二維數組。例如: 數據庫

這裏寫圖片描述

這個二維數組就表明一張有行和列的表結構:

 

  1. 每一行表明一個對象
  2. 每一行全部列的數據表明了一個對象的全部屬性
  3. 每一列固定存儲某一種類型的數據(如:integer、string、date…)。

儘管,二維數組用於存儲表數據很是好,可是當你須要從數組中根據某個條件查詢數據時,性能沒法接受。數組

例如:你想找出在英國工做的全部人,你須要遍歷每一行數據,判斷他是否屬於英國。這個過程須要執行N步操做(N取決於表的行數)。聽起來,性能也不算太差,但有更快的方法嗎?緩存

確定有,接下來就應該樹結構登場了。markdown

備註:現代數據庫使用更高級的數組結構來存儲表數據,如heap-organized tables或者index-organized tables。可是都沒有解決如何在數組中根據一些列的過濾條件快速篩選數據的問題。數據結構

3、Tree and database index – 樹和數據庫索引

 

這裏寫圖片描述

 

這棵樹有15個節點。咱們看一下如何從中找到208這個元素:函數

  1. 從根節點開始查找,根節點是136。由於 136<208,因此在136的右子樹中查找。
  2. 398>208,在398的左子樹中繼續查找。
  3. 250>208,在250的左子樹種繼續查找。
  4. 200<208,在200的右子樹中查找。可是200沒有右子樹。沒有找到208(由於若是能找到的話,它應該在200的右子樹上)。

接着看一下如何查找40這個元素:post

  1. 也是從根節點136開始查詢。由於136>40,因此在136的左子樹中查詢。
  2. 80>40,在80的左子樹中查找
  3. 40=40, 元素找到了!提取40這個節點中存儲的對應數組的行號索引。
  4. 有了這個行號索引,想拿這行的數據,就能當即獲取到(數組的下標訪問)。

最終,兩次查詢的操做步驟數都是樹的高度。若是,你仔細閱讀過merge sort章節,應該知道樹的高度是log(N),因此該查找算法的時間複雜度是O(log(N))。還不錯。性能

(一)Back to our problem – 回到問題上來

文章內容很是抽象,讓咱們回到問題上來。除了簡單的整數型數據,考慮一下字符串,它是用於在前面的表中表示某我的的所屬國家信息的。假設,你已經構建了一個樹,包含前面表中的「country」字段數據。

  1. 你想知道哪些人在UK工做
  2. 你須要查找樹,找到UK的節點。
  3. 在UK節點內,你能找到全部在UK工做人的對應數組行號索引信息。

這個查詢操做僅耗費了Log(N)步操做,而不是直接在數組中查詢所須要的N步。如今你也能猜到數據庫索引是什麼東西了吧?

你能爲任意多列數據創建索引(一列字符串,一列整型數,2列字符串,一列整型 + 一列字符串,一列日期類型等等)。只要你對這些列實現了比較函數,你就能控制主鍵在樹中的排列順序(數據庫已經爲基本數據類型實現了比較函數)。

(二)B+ tree index – B+樹索引

儘管上面的二叉樹在查詢某個固定值時工做得很好,可是若是要查詢某個範圍內的全部數據,性能就很是低。它須要花費N步操做,由於須要比較樹中的每個節點以判斷它是否在指定的範圍內。此外,這種方式也很耗費I/O資源,由於要讀取整個索引樹。咱們須要找到一種高效的範圍查詢方法(range query)。爲了解決這個問題,現代數據庫使用B+樹,B+樹是前面二叉查詢樹的優化。在B+樹裏面:

  1. 只有葉子節點存儲關聯表的每一行數據對象的指針(譯者注:這裏的指針是指能快速找到數據自己的數組行號,哈希表的key等索引,不只是C 語言中的pointer, 下同)。
  2. 其它節點的用途只在查詢的時候,幫助路由到想找的葉子節點。

     

    這裏寫圖片描述

    如上圖所示,B+樹存儲了更多冗餘的節點(2倍)。樹內部多出了一些附屬節點,這些「decision nodes」的做用是幫助你找到正確想要的葉子節點(存儲了表數據指針的節點)。B+樹的查詢時間複雜度仍然是Log(N), 樹僅僅只是多出了一層。最大的差別是,葉子節點中存儲了指向下一個節點的指針。

     

在這個B+樹裏面,若是你查找40到100以前的數據:

  1. 你只須要查詢值爲40的節點(或者比40稍大的節點,若是40的節點不存在的話)。查詢方式跟以前的二叉樹同樣。
  2. 收集40後繼的節點,經過它存儲的指向後繼結點的指針,直到遇到100(或者比100少大的數).

假如,你須要查詢M個節點,樹有N個節點。查詢指定的值(40)的時間複雜度是Log(N), 跟以前的二叉樹查詢同樣。可是,一但你找到了節點(40),你還須要經過M步操做,遍歷收集M個後繼結點。B+的range query的時間複雜度是O(M+Log(N)), 相比以前二叉樹O(N)複雜度,性能提高不少。數據量越大,性能提高越明顯。你不須要讀取整顆樹,這也意味着更小的磁盤I/O讀取。

可是,這也帶來了新的問題(再一次遇到問題)。若是你往數據庫添加或者刪除一行記錄,同時也須要在B+樹中更新數據:

  1. 你須要保證B+樹中的節點順序,不然你沒法在一個混亂的樹中查找節點。
  2. 你必需要保證葉子節點的從小到大的順序排列,不然range query的時間複雜度將由O(Log(N))退化爲O(N)。

換句話說,B+樹必需要有自我調整樹平衡性和節點順序的能力。謝天謝地,智能化的數據刪除和數據插入操做使得B+樹的能保持以上特徵。這也帶來了成本:插入和刪除數據的時間複雜度是O(Log(N)), 這也是爲何你常常會聽到這樣一種觀點:索引太多不是什麼好事。實際上,這會下降插入/更新/刪除操做的效率,由於數據庫須要同時更新表的索引,每一個索引花費O(Log(N))的時間。

譯者注:凡事都有兩面,有利必有裨;選擇什麼樣的數據結構,是根據你的應用場景來的。

另外,索引也會增長tansaction manager的複雜度(最後一張將講到tansaction manager)。

更多的細節,你能夠在維基百科上搜索B+ Tree。若是你想要一個B+樹實現的樣例,你能夠讀一下這篇文章(https://blog.jcole.us/2013/01/07/the-physical-structure-of-innodb-index-pages/), 這篇文章的做者是MySQL的核心開發人員。他詳細講述了innoDB(MySQL數據庫引擎)是如何實現索引的。

4、Hash Table – 哈希表

最後一個重要的數據結構是hash table。當你須要快速查找一個數據時,hash table很是有用。另外,理解了hash table將幫助咱們理解後面將提到的一種經常使用數據庫鏈接技術:hash join。Hash table也常常用於存儲一些數據庫的內部管理數據,如lock table,buff pool等。這些概念在後面都會講到。

Hash table是一種能根據關鍵字快速查找數據的數據結構類型。構建hash table須要定義以下一些內容:

1) 對象關鍵字 
2) 爲關鍵字定義的哈希函數(hash function)。對象的關鍵字用哈希函數計算的結果表示了對象存儲的位置(稱爲buckets)。 
3) 關鍵字比較函數。一旦找到了對象所在的bucket,接下來須要在bucket內部,經過比較函數找到對應的對象。

(一) A simple example - 一個hash table的樣例

 

這裏寫圖片描述

這個hash table有10組bucket。我只在圖中畫了5組bucket,另外5組 請自行腦補。Hash function我定義爲除10求模(除以10求餘數),換句話講,我能夠經過數值的最後一位數字肯定bucket。

 

  1. 若是數字的最後一位是0,將數值存儲到bucket 0中
  2. 若是數字最後一位是1,將數值存儲到bucket 1中
  3. 若是數字最後一位是2,將數值存儲到bucket 2中
  4. …..

比較函數我採用判斷兩個整型數值是否相等的方法比較。

咱們看一下如何找到hash table中找到元素78:

  1. 經過哈希函數計算獲得哈希值8
  2. 在bucket 8中查找元素,第一個元素就是78
  3. 返回元素78
  4. 查詢只須要執行兩個步驟:第一步是計算哈希值,肯定bucket位置;第二步在bucket中查看元素。

咱們再看一下如何查找元素59:

  1. 計算哈希值,獲得9
  2. 在bucket中查找元素59。第一個元素是99,99!=59,99不是要查找的元素。
  3. 採用一樣的方式,查找第二個元素(9),第三個元素(79),… 最後一個元素(29)。
  4. 不存在59這個元素
  5. 本次查詢執行了7步操做

(二)A good hash function – 一個好的哈希函數

如你所見,查詢不一樣的值,時間複雜度是不一樣的。

若是你將哈希函數改成除以1000000取模(取數字的最後6位數,做爲bucket標識)。上面的第二次查詢只須要一步(在bucket 59中沒有任何數據)。找一個好的哈希函數,保證每一個bucket中存儲儘量少的數據,是很是困難的。

在上面的例子中找一個好的hash function很是容易。但這僅僅是一個簡單的樣例,若是關鍵字是以下數據類型,將很是困難: 
1. 一個字符串(例如表示人的名) 
2. 兩個字符串(例如同時表示人的姓和名) 
3. 兩個字符串 + 一個日期(例如表示人的姓、名及生日)

設計一個好的hash function,哈希表的查詢時間爲O(1)。

5、Array VS Hash table

爲何不使用數組? 問得好。

  1. Hash table支持將部份內容加載到內存,另外一部分保存在磁盤上。沒必要把整顆樹加載到內存,節省內存空間。
  2. 數組必須使用連續的內存空間。若是要加載一張大表數據到內存,很難找到一大片連續的內存空間。內存分配失敗的風險很大。
  3. Hash table支持選擇你想要的任意字段做爲關鍵字(例如:人的所屬國家,加上人的姓名。任意組合)。

想了解更多的信息,你能夠讀一下介紹Java如何實現hash map的文章,它是hash map高效實現的一個樣例。理解本文中概念,你不須要懂JAVA。

已翻譯的《How does a relational database work》其它章節連接: 
1. 關係型數據庫工做原理-時間複雜度:http://blog.csdn.net/ylforever/article/details/51205332 
2. 關係型數據庫工做原理-歸併排序:http://blog.csdn.net/ylforever/article/details/51216916 
3. 關係型數據庫工做原理-數據結構:http://blog.csdn.net/ylforever/article/details/51278954 
4. 關係型數據庫工做原理-高速緩存:http://blog.csdn.net/ylforever/article/details/50990121 
5. 關係型數據庫工做原理-事務管理(一):http://blog.csdn.net/ylforever/article/details/51048945 
6. 關係型數據庫工做原理-事務管理(二):http://blog.csdn.net/ylforever/article/details/51082294

相關文章
相關標籤/搜索