一個小型數據庫的核心組件

若是想要了解存儲,我比較推薦的方式仍是從瞭解數據庫開始。從目前來看,數據庫發展了這麼多年,各類理論相對的比較完善,面對各類應用場景,其核心處理模式也已經很是的成熟了,在新的海量數據的時代,人們只是對擴展性提出了更高的要求,而對數據存儲的其餘方面卻仍然但願能保持以前的水平。

而從目前實際的發展來看,基本上目前發展的核心思路並無繞開人們在數據庫理論領域內所積累的那些關鍵的特性。所以,若是你但願可以快速的在海量數據的在線處理領域內積累知識,從傳統數據庫領域入手是絕對不會錯的。java

下面,就讓咱們對數據庫作個簡單的解刨,看看數據庫裏面有哪些核心的組件吧。程序員

映射(Map):
首先就須要有可以存儲數據並提供查詢的結構,這個結構,在java裏面就是Map。C裏面也是Map.他的核心做用就是,創建一種key與value的映射關係,當給定某個key的時候,他可以返回這個key所對應的value給用戶。這是用戶在進行查詢時的主要數據結構。算法

預寫式日誌(write-aheadlogging,WAL):
就是個隊列,記錄了你每一次寫的操做。天然而然的,由於你的每次寫操做都被記錄下來了,因此就算計算機斷電了,只要這個日誌沒有損壞,計算機重啓後按照這個log,重放在斷電時的那些寫操做,就能夠保證你的數據不丟。
這裏,必定會有人問:既然我數據都存儲在k-v表裏了,明顯就不會丟失了。爲何還要有這個log呢?這其實就是一個計算機的本質性問題了,別看現代計算機運算速度這麼快,他終歸也只是個「圖靈機」實現,或者更具象化一點,就是一臺打字機,一次只能打一個字母,那麼可能會有人問了,若是我要用幾個字母來表示同一個意思,應該怎麼作呢?在英語中,最簡單的方式就是在詞組和詞組之間增長空格。好比writeaheadlogging.就是三個由字母組成的單詞。在計算機裏,也有相似的問題,用戶的一次寫入操做,可能對應計算機內的多步操做,如何可以保證這屢次的操做要麼所有成功,要麼所有失敗呢?WAL就是個解決的方法,他利用的是操做系統裏的一個原子操做fsync().該操做的做用是將一小段數據寫入到磁盤,從而保證數據不會丟失。
咱們來看一下總體的操做思路:記錄用戶的寫入操做(insert,update,delete)->進行內部屢次key-value映射的構建,包括主數據,輔助索引數據等->標記該用戶操做完成。sql

觸發器(trigger)
一個不難理解的概念,當發生insert,update,delete等操做的時候,可能會有一些需求須要依託這些操做而被觸發執行其餘的操做。好比每一行鍼對錶A的更新,都會引起B表內的更新。那麼這個「引起」的過程,就是觸發器。在一些其餘的語言裏面,這也被叫作callback,IFTTT,Listener等。但核心概念都同樣,被動的由於某個事件而觸發一段代碼邏輯的運行。
在一些數據庫的實現中,甚至二級索引的更新也是使用觸發器來完成的哦:)
在數據庫內,觸發器所有是同步實現的,也就是說,只有當數據寫入的操做,以及觸發器的操做所有都執行完成後,纔會返回用戶執行成功。數據庫

鎖(lock)
鎖的主要目標是容許線程圈定一批資源,並規定該資源只容許發出圈定請求的那個線程進行訪問,而其餘線程則必須等待。
這個概念產生的主要緣由其實仍是與計算機是圖靈機有關。。原本計算機就是臺圖靈機,一個時鐘週期內只能打一個字母,但這樣他就很難同時作好幾件事情,好比聽着歌寫代碼,這件事其實從計算機硬件來講是作不到的,他只能模擬,利用時分複用的方式,把cpu的運算分解成小片,每一個線程都只佔用一小段時間,從而可以作到同一時間作好幾件事。可是,想想,若是咱們但願一我的A用打字機打iamgod.而但願另一我的B用同一臺打字機打pigismoney.開始,時間片分配給A,他打印了iam後,A被cpu換出,B被換入,打印了pig後被其餘人換出,那麼咱們天然就發現。。數據就變成了。。。那麼鎖的做用就是保證一個邏輯的原子操做沒有完結的時候,這張打印紙只屬於A,其餘人不能對其進行訪問或進行修改。
明白了原理,來簡單看看實現,鎖主要是由排他鎖(寫鎖)和共享鎖(讀鎖)構成,在數據庫的鎖實現中,有不少針對共享鎖和排他鎖相互組合的細節性描述,但其核心的問題卻永遠沒變:
1)儘量的減小同一時間內被阻塞的線程數,從而提高並行度。
2)儘量的避免死鎖
能夠說數據庫實現的是好是壞,關鍵就看着鎖的優化好很差,這在分佈式場景或者在單機內都是最重要的一個機制。編程

執行優化器
這是關係數據庫得名的緣由,主要的做用是將關係查詢轉換成key-value查詢,輸入是sql的抽象語法樹(ast),輸出則是執行計劃,就是各位在數據庫命令行打explainsql時候出來的那些東西。
理解上很簡單,但實際上實現起來倒是最爲複雜的,在上個世紀,大部分的執行優化器使用rulebasedoptimizer,也就是基於規則的優化,但在現代數據庫實現中,大部分的優化器都採起了costbasedoptimizer了,他們之間最大的不一樣,就是cbo更多的考慮了數據實際的區分度狀況,從而能更簡單準確的從。多個可選的索引中選擇一個正確的索引。緩存

sql解析器
做用很簡單,把用戶輸入的sql轉化爲計算機能夠理解的抽語法樹(不懂就去看編譯原理:)安全

好了,基本組件兒介紹完畢,下面咱們利用這些核心組件來嘗試拼裝一些外圍的概念。服務器

第一個概念是:存儲過程。
我第一次接觸數據庫的時候,對存儲過程比較不理解。認爲數據庫麼,使用關係模型就足夠了啊,爲何還要支持一種相似編程語言的東西來額外的增長系統的複雜度呢?並且在當時,有大量的高級程序員在介紹他們的經驗的時候都會分享說:儘量不使用存儲過程,那玩意兒很是不容易維護,也會增長很是多的使用成本,應該把全部業務邏輯放在客戶端。那麼我天然就有個疑問,既然這些事情客戶端都能作,那麼還要存儲過程幹什麼?可能第一次接觸數據庫的人也會有我以前的困惑吧。。。呵呵,因此既然我已經能解答這個問題,在這裏天然而然的也要嘗試給有相同問題的人解惑。
存儲過程其實不是個複雜的概念,他的核心目標就是讓數據庫端可以運行邏輯代碼(判斷,循環..etc),甚至在oracle,存儲過程能夠作任何事。咱們排除oracle但願用戶只用數據庫來完成一切功能的陰謀論,來看看事情的本源是什麼?或者說,有什麼事情是存儲過程能作,而其餘方式作不了的?
很簡單,也有不少人提到過,就是性能好。那麼,爲何會性能好呢?
這與咱們目前的軟件結構有關係,在當前,大部分狀況下,數據庫是一臺獨立的機器,而應用服務器則是另一臺獨立的機器,那麼,相互獨立的機器之間要進行交互操做,勢必須要使用網絡來進行通訊。
網絡通訊的代價比使用內存指針變動的代價大很是多,這就致使了一個直接的問題,若是使用網絡進行屢次交互,那麼延遲會遠遠地大於使用內存來進行消息交互。延遲變大,意味着鎖持有時間變長,也就意味着單位時間內針對同一個數據的操做頻率降低,TPS就會降低。
這纔是存儲過程之因此可以提高性能的關鍵。它不是惡魔,但也不是天使,能不能發揮出特定的優點,要看具體的業務場景須要。
咱們作個簡單的總結:
存儲過程的好處,就是能夠減小網絡交互開銷,能夠用來封裝一些須要高性能的小的業務邏輯單元。
存儲過程的壞處,就是綁定到特定數據庫上,同時,由於大部分存儲過程是面向過程的代碼,因此運維難度相對較大,不適於處理複雜業務邏輯。
第二個概念是:視圖
視圖這個概念也是我開始看數據庫時候很暈的一個概念,在任何一個數據庫內,數據庫的說明文檔中都會給出特別多中視圖的實現,看起來就特別容易暈。常常有的困惑是:爲何視圖不能寫數據?以及,join自己也挺方便的的,我爲何還須要視圖?
這裏,爲了解答這個問題,咱們就須要來看看一種最多見的計算機優化方法:將不肯定性變成肯定性。
不少狀況下,若是你能提早預知不肯定性的範圍,每每就能大範圍的減小鎖的範圍,或者將計算量進行分解。
視圖,從必定程度上也是利用將不肯定性變成肯定性的方式,來實現join查詢速度的優化和聚焦。
若是計算機不知道你預先須要把哪些表進行join操做,他能作的就只有使用最悲觀的方式來對用戶的行爲進行假定,也就是最壞狀況下,全部表均可能產生關聯關係,而且關聯的次數和頻率都是均等的。那麼針對這種場景,最安全的策略就是不緩存任何join的中間結果,而只使用通用的join算法進行join計算。
可是,若是用戶經過本身的實際業務場景,發現其實有兩個表是固定的被join在一塊兒而進行查詢的。這種狀況就符合了」將不肯定性變成肯定性「這個優化的前提,所以就能夠進行一些優化,view從某種程度上來講,就是告知數據庫這種肯定性的一種手段。
數據庫在獲知這種hint後,就可使用一些新的,空間換時間的方式,來預先進行一些操做,從而下降在join查詢計算髮生時所消耗的計算量。從而提高查詢性能,下降系統開銷。
ok,本篇到這,本篇主要是介紹了數據庫的一些關鍵的概念,在下一篇,我將使用一些實際查詢的例子,來幫助你們更易於理解在實際數據庫中,上面的這些核心概念是如何被應用的。
相關文章
相關標籤/搜索