乙個資料庫最基本的要具有兩個功能:當你給它一些資料的時候它可以幫你儲存資料,之後當你需要這些資料時,他可以返回給你所需要的資料。
你(應用程式開發人員)向資料庫提供固定格式的資料,稍後你就可以再次請求獲取這些資料。 在本章中,我們將從資料庫的角度討論以下問題:資料庫如何儲存我們所給出的資料,以及當我們需要這些資料時,我們如何再次從資料庫裡找到它,即資料庫內部是如何儲存和檢索資料的。
你可能會問,為什麼作為乙個應用開發人員需要關心資料庫的儲存和內部檢索實現?畢竟我們基本不可能從頭開始去實現自己的資料儲存引擎,因為已經用眾多可以直接使用的方案。此外針對事務性工作負載進行優化的儲存引擎和為分析型工作負載進行優化的儲存引擎之間存在很大的差別。
首先我們將從這一章開始討論儲存引擎,這些儲存引擎可能在你熟悉的各種資料庫中使用:傳統的關聯式資料庫,以及大多數所謂的no sql資料庫。我們將主要介紹這兩個家族的儲存引擎。
乙個最簡單的儲存引擎的實現如下:
#!/bin/bash
db_set (
)db_get (
)
這兩個函式實現乙個鍵值儲存。您可以呼叫db_set key,它將在資料庫中儲存key和value。key和value可以是(幾乎)任何你想要的東西——例如,value可以是乙個json文件。之後你可以呼叫db_get key,查詢與該特定key關聯的最新值並返回它。
底層儲存格式非常簡單:乙個文字檔案,其中每一行都包含了乙個key-value對,用逗號分隔(大致像csv檔案一樣)。每個對db_set的呼叫都會將資料追加到檔案的末尾,因此,如果你更新了乙個key,那麼這個key的舊版本不會被覆蓋——你需要檢視該檔案中這個key所對應的所有歷史value值,並找到最新的value。
真正的資料庫有更多的問題需要處理(比如併發控制,磁碟空間**,這樣日誌就不會永遠增長,處理錯誤等等),但是基本原理是一樣的。日誌格式的資料儲存非常有用,我們將在之後的其餘部分中多次遇到它們。
( 「日誌」一詞通常用來指應用程式日誌,其中應用程式輸出用於描述正在發生的事情的文字。在這本書中,日誌具有更一般的意義:乙個附加的記錄序列。它不需要是人類可讀的;它可能是只用於其他程式的讀取的二進位制的檔案。)
另一方面,如果資料庫中有大量的記錄,那麼db_get函式的效能會很糟糕。每次你想要查詢乙個key時,db_get將從頭到尾掃瞄整個資料庫檔案,查詢key對應的所有記錄。在演算法術語中,查詢的成本是o(n):如果你的資料庫中的資料量增加時,那麼查詢所需的時間將成倍增長。
為了有效地發現資料庫中某個key對應的value,我們需要不同的資料結構:索引。在這一章中,我們將研究一系列的索引結構,並對其進行比較; 它們背後的基本思想是將一些額外的元資料單獨儲存,充當路標,幫助你定位所需的資料。如果您想以幾種不同的方式搜尋相同的資料,您可能需要在資料的不同部分上使用幾個不同的索引。
索引是派生自原始資料的附加結構。許多資料庫允許您新增和刪除索引,這並不影響資料庫的內容;它只影響查詢的效能。 維護額外的資料結構是有代價的,特別是在寫操作上,對於寫操作來說,無法在像之前那樣簡單將記錄新增到檔案中,任何型別的索引通常會減慢寫操作,畢竟在寫資料時索引也需要更新。
這在儲存系統中是乙個重要的權衡:選擇良好的索引可以加快讀取速度,但是每個索引都減慢了寫入速度。 出於這個原因,資料庫通常不會在預設情況下索引所有內容,但是需要你(應用程式開發人員或資料庫管理員)結合你對應用程式主要查詢模式的了解手動構建索引。 然後,你可以選擇為你的應用程式構建最優的索引,而不必引入不必要的開銷。
讓我們從key-value資料的索引開始。這不是唯一可以作為索引實現的資料結構,但它非常常見,而且它是更複雜索引重要的組成部分。鍵值儲存與大多數程式語言中可以找到的dictionary型別非常相似,通常是作為雜湊表(hashtable)實現的。hashtable在許多演算法教科書中都有描述[1,2],因此我們不會詳細討論它們在這裡是如何工作的。既然我們已經有了記憶體資料結構的雜湊對映,為什麼不使用它們來索引磁碟上的資料呢?
假設我們的資料儲存只包括對檔案的追加,就像前面的例子一樣。 那麼,最簡單的索引策略是:儲存乙個記憶體雜湊對映,其中每個key都對映到資料檔案中的乙個位元組偏移位置,該位置儲存有對應的數值,如圖3-1所示。
無論何時將新的key-value對新增到檔案中,您都會更新hashtable以反映您剛剛編寫的資料的偏移量(這既適用於插入新鍵,也用於更新存在鍵)。當你想查詢乙個key時,使用hashtable來查詢資料檔案中的偏移量,定位該位置,並讀取該值。
這聽起來可能過於簡單,但卻是可行的方法。這實際上是(bitcask 中的預設儲存引擎)所做的[3]。
bitcask 提供高效能的讀和寫,需要滿足的前提就是所有的key都儲存在記憶體裡,因為hashtable被完全保留在記憶體中。 但是value並沒有存在記憶體裡,而是儲存在磁碟上,所以value可以使用比可用記憶體更多的空間。如果資料檔案的那一部分已經在檔案系統快取中,那麼讀操作將不會有任何磁碟i/o。
到目前為止,我們一直對檔案進行了追加,那麼我們如何避免最終耗盡磁碟空間呢? 乙個好的解決方案是,當它達到一定的大小時, 通過關閉這個檔案,並將寫入到乙個新的檔案中。這樣每個檔案中儲存的都是一小段資料,稱之為:data file segment。然後我們可以對這些檔案中的內容進行壓縮,如圖3-2所示。壓縮意味著將檔案中重複key合併,並且只保留每個key的最新值。
此外,由於壓縮常常使segment更小(假設乙個key在乙個segment內平均被重寫了多次),我們也可以同時將所有的segment合併在一起執行壓縮,如圖3-3所示。segment 在寫入後不會被修改,因此將合併之後的segment寫入到乙個新segment中。segment的合併和壓縮可以在後台執行緒中完成,而在壓縮合併進行的過程中,我們仍然可以使用舊的segment段檔案,以正常的方式完成讀和寫。合併壓縮過程完成之後,我們將讀請求改為讀取新合併的segment而不是舊的segment,然後舊的segment就可以被刪除掉以釋放磁碟空間。
每個segment都有自己的記憶體hashtable作為索引。為了找到key對應的value的值,我們首先檢查最近的segment的hashtable。如果key不存在,我們將檢查第二個最近的部分,等等。segment合併的過程減少了segment的數量,所以查詢操作不需要檢查太多的hashtable。這個簡單的想法在實踐中有很多細節。簡單地說,真正實現的一些重要問題是:
如果segment檔案只是附加的或不可變的,那麼併發控制和崩潰恢復要簡單得多。例如,您不必擔心當乙個值被覆蓋時發生失敗後的處理操作,因為如果在覆蓋時發生失敗,那麼留給你的將是乙個不完整的資料檔案。
合併舊segment可以避免乙個問題:隨著時間的推移資料檔案變得很零碎很分散。
然而hash 索引也有它的侷限性:
範圍查詢無效。例如,你不能輕鬆地掃瞄kitty00000和kitty99999之間的所有key,您必須在hashtable中查詢每乙個key。
未完待續。。。。
(DDIA)資料儲存與檢索(三) B tree
目前我們所討論的日誌結構的索引已經被廣泛認可,但是他們卻不是最普遍的索引型別。被用於構建索引的最普遍的資料結構於此有很大的不同,我們稱之為 b tree 在1970年引入,不到10年之後,已經發展到 無所不在 b trees經受住了時間的考驗。它們仍然是幾乎所有關聯式資料庫中的標準索引實現,而且許多...
DDIA讀書筆記 3 資料儲存與檢索
構建和維護sstables 如何避免資料庫崩潰時,在記憶體中的記憶體表資料丟失?lsm儲存引擎 log structured merge tree 基於合併和壓縮進序檔案原理的儲存引擎 lsm補充 lucene中,從詞條到posting list的對映關係儲存在類sstable的排序檔案中,這些檔案...
關於資料的儲存與檢索
1.儲存資料的兩種方式 普通檔案 flat file 和資料庫 2.php中開啟檔案用放fopen 函式 do cume ntro ot server document root php內建變數 fp fop en document root orders orders.txt w 第二個引數為檔案...