存儲分爲volatile和non-volatile,越快的越貴越小數據庫
那麼因此要解決的第一個問題就是,若是儘可能在有限的成本下,讓讀寫更快些緩存
意思就是,儘可能讀寫volatile存儲,可是volatile比較頗有限,因此須要合理的在兩種存儲上去swap數據結構
可是技術是在飛速的進步的,因此如今有Non-volatile memory併發
因此最近流行內存數據庫,由於當前的memory和磁盤間的IO瓶頸已經消除,因此當前的瓶頸是CPU cache和Memory以前的問題性能
這個問題會在下一門課裏面說優化
因此繼續前面的問題,怎麼解決disk和memory之間的IO瓶頸編碼
一個直覺的想法就是,交給操做系統去作,使用虛擬內存,Virtual Memoryspa
mmap能夠產生內存文件,把磁盤文件的內容map到內存的地址空間,這樣有個問題就是若是有多個併發寫,須要同步機制,系統也提供右圖這些同步指令操作系統
可是數據庫管理系統每每但願作的更精細,由於操做系統是個通用方案,必定達不到性能最優設計
下面咱們來看第二個問題,DBMS如何將數據庫的數據放到磁盤文件上?
這裏有個選擇,DBMS是否要用系統的文件系統,仍是拿一塊raw storage本身管理,如今通常的選擇是仍是使用文件系統,畢竟方便
既然用文件系統,那麼DBMS就須要把數據庫數據存成一個或多個文件
這裏有個概念,Page,文件是由一堆page組成的
page其實就是固定大小的數據塊,那爲何要有這層抽象?
這個和咱們使用的存儲有關,當前用的磁盤,除了慢,還有個特色是對順序讀寫比較友好,由於隨機讀須要磁頭不斷的機械移動的,這個想一想也很慢
因此文件系統和磁盤間的IO,須要儘可能批量讀,讀寫數據的最小單位稱爲數據塊,通常是4K,爲何是4K,應該是由於比較經濟
而數據庫的page是基於文件系統的,因此設計成4k的倍數會比較合理
數據庫會本身維護一個page id到實際存儲地址(文件+offset)的映射
那麼如何在磁盤文件上管理page?
有三種方式,最多見的是Heap FIle
HeapFile就是用來放page的文件,固然咱們能夠經過文件名+offset,訪問某個page
同時咱們須要能夠遍歷全部的page,知道哪些page有free space能夠用來存放tuples
因此這裏heap file也有兩種實現方式,
繼續看看Page的構造是怎麼樣的?
能夠看到在page中的header,存儲了一些元數據,若是須要self-contained,就須要包含scheam,編碼信息等
那麼data,是如何組織的了?
其實有兩種方式存儲數據庫的數據,
Tuple-oriented和Log-structured
Tuple-oriented主要的存儲方式是,slotted pages
這個方法關鍵就是加入了slot array來索引各個tuple,這樣就能夠兼容變長的tuples,否則怎麼知道每一個tuple從哪裏開始,刪除tuple也更簡單
若是是Log-structured,寫數據會比較簡單
但讀數據就比較麻煩了,須要replay出數據,由於你只記錄了log嗎
提供讀性能的方式有兩種,儘可能減小replay的數據,就是打snapshot或建index
比較經常使用的就是按期的作compaction,好比HBase, Cassandra,LevelDB,RocksDB
Compaction分爲兩種,按層逐級compact,或是universal
最後,tuple自己的存儲結構是怎麼樣的?
一樣Tuple也有一個header,裏面包含元數據,好比這個tuple可見性,BitMap表示哪些是NULL
注意這裏通常是不會包含schema,由於在每一個tuple都包含沒有必要,一個table的schema是固定的,單獨存就好
Denormalized Tuple Data
這是一種針對查詢的優化,
Denormalized,都知道關係模型有範式,冗餘數據必定是會打破範式的,因此是de-
兩個表join,把須要的字段冗餘到一張表中,稱爲pre join,讀的時候會比較快,單純從當前page就能夠完成,可是寫就麻煩了,由於打破範式了嗎
總結一下上面的說的,以下圖
這裏page管理用的是direction的方式,因此讀取page2,
首先要把direction page加載到buffer裏面,這樣讀到page2的地址而後再去讀出page2,而後Execute engine需求去解析page
Page中就是tuple的集合,tuple是a sequence of bytes,但若是咱們要使用這些數據,首先要把這些bytes轉化爲相應類型的數據
主要的類型以下,
須要特別關注的,
浮點數和定點數
定點數就是小數點是固定的,因此咱們用int分別存儲小數點先後的數字就能夠實現,定點數是能夠作到精確計算的,可是侷限也很明顯,只能表示固定精度
浮點數就比較複雜了,由於小數是連續的,無限的,而計算機實現是離散的,有限的
因此要在計算機裏面表示浮點小數,就須要用trick的方法去近似,定義出的標準就是IEEE-754,浮點運算是近似的,非精確的
VARCHAR,BLOB
因爲tuple大小不能超過page,好比對於varchar,若是大於page,須要把多的存放到overflow page裏面
而對於blob這樣的類型,乾脆就須要存放到外部文件中,這裏注意對於存放到外部文件的數據,是不保證transaction等語義的
那麼如今有個關鍵的問題,數據庫的元數據是存儲在什麼地方的?
Catalogs,Catalogs的信息能夠從Information_schema表中讀取到
不一樣的庫,對於元數據讀取有不一樣的shortcuts,
最後再看下,行列存的區別,
行存,row storage,稱爲n-ary storage model
對於左圖的OLTP的需求,行存很適合,插入和更新比較簡單,整行的查詢
但對於右圖,OLAP的需求,行存會比較慢,BI需求每每須要掃描大量的行,但只是用其中的部分字段
列存,column store,decmposition storage model
相對於行存,列存是把一列的數據集中存儲在一個page中,
這樣上面的例子,就只須要讀包含這兩個列的page,其餘page就不用讀了
列存關鍵的問題是如何恢復成行?
這裏給的方法也很簡單,若是列中的每一個value都是等長的,那直接根據length除就知道是第幾行的
或者,就是在每一個列裏面記錄下tupleid
bufferPool是一種cache機制,讀磁盤慢,因此把讀到的page緩存在bufferPool的frame裏面
而且用Page Table來記錄,到底哪些page在bufferpool中;page table中還會記錄meta,好比dirty flag,這個page被改過,不能直接drop掉,須要寫回磁盤;Pin,這個page正在被讀,不能被swap out
上面的鎖的形狀,表示latch,
我要讀page2,table中miss,那麼先用latch鎖定一個slot,而後等page2被load到bufferPool的frame的時候,link上,這個過程當中別人不能來修改或讀取這個slot
下圖,表示在數據庫領域,lock和latch的區別,
lock是應用層面的,對邏輯內容的互斥,好比行,表,庫,事務
latch是應用不可見的,內部數據結構的互斥
同時,若是一個數據庫只有一個bufferPool,由於全部和磁盤間的數據交換都要經過他,很容易爭搶,解決的方式,
咱們能夠用多個bufferPool,按不一樣的用途,維度區分開
BufferPool在cache的時候有些優化
Pre-Fetching
預取,這個就是dbms本身作cache的好處,你讓os作cache,它無法去知道你下面可能要讀什麼
Scan Sharing
簡單的說,就是Query之間能夠共享已經cache的page
Buffer Pool ByPass
防止scan操做會污染buffer pool,因此單獨開塊內存去cache query級別的緩存
OS Page Cache
OS在文件系統操做的時候,自己會有page cache
既然dbms在buffer pool已經本身管理了page cache,那麼os的這封cache顯的有些多餘,因此通常數據庫都會用direct IO,把OS的page cache給關掉
不關掉有個好處,好比db進程重啓了,但這個時候os的page cache還在,能夠避免冷啓動
Buffer Replacement Policy
LRU,這個每一個page都有個最後訪問的時間戳,淘汰最老的,但這個須要按時間排序;
Clock,對LRU的近似,更簡單
LRU和Clock對於sequential scan都支持很差,scan很容易就會把以前的buffer給沖掉
因此能夠用LRU-K,記錄下history,算訪問interval,這樣scan這種只訪問一次的,就很容易被淘汰掉
更特化的策略,好比priority hints,dbms知道哪些page比較重要,常常訪問,打上標籤
Dirty Page
page被修改過,就不能直接drop掉,主要flush回disk;
若是每次等eviction的時候再去flush髒頁,會讓eviction的過程很是的慢,因此通常會有個後臺進程按期批量的去刷髒頁
最後dbms除了有buffer pool來cache tuples和indexes,還有其餘的一些memory pool,