數據庫三種範式以下。算法
範式 | 描述 | 反例 |
---|---|---|
第一範式 | 每一個字段都是原子的,不能再分解 | 某個字段是JSON串,或者是數組 |
第二範式 | 1) 表必須有主鍵,能夠是多個順序屬性的組合。2) 非主屬性必須徹底依賴主屬性(這裏指的是組合主鍵),而不能部分依賴。 | 好友關係表中,主鍵是關注人ID和被關注人ID,表中存儲的姓名等字段只依賴主鍵中的一個屬性,不徹底依賴主鍵 |
第三範式 | 沒有傳遞依賴(非主屬性必須直接依賴主屬性,不能間接依賴主屬性) | 在員工表中,有部門ID和部門名稱等,部門名稱直接依賴於部門ID,而不是員工ID |
通常工程中,對於數據庫的設計要求達到第三範式,但這不是必定要遵照的,因此在開發中,爲了性能或便於開發,出現了不少違背範式的設計。如冗餘字段、字段中存一個JSON串,分庫分表以後數據多維度冗餘存儲、寬表等。數據庫
關於B樹和B+樹的概念,請見漫畫算法:什麼是 B+ 樹?數組
下面來看一下數據庫中主鍵索引對應的B+樹的邏輯結構。併發
該結構有幾個關鍵特徵(與通常的B+樹有點不一樣)異步
基於B+樹的這幾個特徵,就能夠很容易的實現範圍查詢、前綴匹配模糊查詢、排序和分頁(查詢條件應是索引)。這裏有幾個要注意的問題。性能
select *** where *** limit 100, 10
,數據庫須要遍歷前面100條數據才知道offset=100的位置在哪。合理的分頁方法就是不使用offset,把offset變成條件來查詢。好比變成select *** where *** and id > 100 limit 10
,這樣才能利用索引的遍歷,快速定位id=100的位置。事務的四大特性ACID。設計
事務與事務併發地操做數據庫的表記錄,可能會致使下面幾類問題。日誌
問題 | 描述 |
---|---|
髒讀 | 一個事務A讀取了另外一個未提交的事務B的數據,可是事務A提交以前,事務B又回滾了,致使事務A剛剛讀到的就是一個髒數據(RC隔離級別可解決)。 |
不可重複讀 | 同一個事務兩次查詢同一行記錄,獲得的結果不同。由於另外一個事務對該行記錄進行了修改操做(行排它鎖可解決)。 |
幻讀 | 同一個事務兩次查詢某一範圍,獲得的記錄數不同,由於另外一個事務在這個範圍內進行了增長或刪除操做(臨鍵鎖可解決)。 |
丟失更新 | 兩個事務同時修改同一行記錄,事務A的修改被後面的事務B覆蓋了(須要本身加鎖來解決)。 |
下面來看一下InnoDB的事務隔離級別。可解決上面的三個問題,最後一個問題只能在業務代碼中解決。code
名稱 | 描述 |
---|---|
READ_UNCOMMITTED(RU) | 跟沒有同樣,幾乎不使用。 |
READ_COMMITTED(RC) | 只能讀取另外一個事務已提交的事務,能防止髒讀。 |
REPEATABLE_READ(RR) | 可重複讀(在一個事務內屢次查詢的結果相同,其它事務不可修改該查詢條件範圍內的數據,會根據條件上Gap 鎖) |
SERIALIZABLE | 全部的事務依次逐個執行,至關於串行化了,效率過低,通常也不使用。 |
關於數據庫的鎖,請詳見這篇。MySQL中的「鎖」事cdn
爲了保證數據的持久性,須要每提交一個事務就刷一次磁盤,可是這樣效率過低了,因此就有了Write-Ahead。
Write-Ahead:先在內存中提交事務,而後寫日誌(在InnoDB中就是redo log,日誌是爲了防止宕機致使內存數據丟失),而後再後臺任務中把內存中的數據異步刷到磁盤。
從邏輯上來說,日誌就是一個無限延長的字節流,從數據庫啓動開始,日誌就一直在增長,直到數據庫關閉。
在邏輯上,日誌是按照時間順序從小到大用LSN(是一個64位的數)來編號的,由於事務有大有小,因此日誌是個變長記錄(每一段數據量都不同)。
從物理上來說,日誌不多是一個無限延長的字節流,由於每一個文件有大小限制。在物理上是整塊的讀取和寫入(這裏就是Redo Log 塊,一個塊就是512字節),而不是按字節流來處理的。並且日誌是能夠被覆寫的,由於當數據被刷到磁盤上後,這些日誌也就沒有用了,因此他們是能夠被覆蓋的。可循環使用,一個固定大小的文件,每512字節一個塊。
在InnoDB中,Redo Log採用先以Page爲單位記錄日誌(物理記法),每一個Page裏再採用邏輯記法(記錄Page裏的哪一行修改了)。這種記法就叫作Physiological Logging。
之因此採用這種記法,是邏輯日誌和物理日誌的對應關係決定的。
由於不一樣事務的日誌在Redo Log中是交叉存在的,因此無法把未提交的事務與已提交的事務分開。ARIES算法的作法就是,無論事務有沒有提交,它的日誌都會被記錄到Redo Log上並刷到磁盤中。當崩潰恢復的時候,會把Redo Log所有重放一遍(不論是提交的仍是未提交都都重作了,也就是徹底恢復到崩潰以前的狀態),而後再把未提交的事務給找出來,作回滾處理。
其實事務的回滾都是反向提交。也就是根據事務中的SQL語句生成反向對應的SQL語句執行,而後Commit(這種逆向的SQL語句也會被記錄到Redo Log中,防止恢復中宕機,可是會與正常的日誌區分開),因此回滾是邏輯層面上的回滾,在物理層面實際上是個提交。
以下圖,有六個事務,每一個事務所在的線段表示事務在Redo Log中的起始位置和結束位置。發生宕機時,須要回滾事務T三、T四、T5。
在圖中,綠線表示兩個Checkpoint點和Crash(宕機)點。藍線表示三個階段工做的起始位置。
在分析階段,要解決兩個問題。
1)肯定哪些數據頁是髒頁,爲階段2的Redo作準備(找出從最近的Checkpoint到Crash之間全部未刷盤的Page)。
2)肯定哪些事務未提交,未階段3的Undo作準備(由於未提交的事務也寫進了Redo Log中,須要將這些事務找出來,並作回滾)。
ARIES的Checkpoint機制,通常使用的是Fuzzy Checkpoint,它在內存中維護了兩個表,活躍事務表和髒頁表。
1)活躍事務表:當前全部未提交事務的集合,每一個事務維護了一個關鍵變量lastLSN(該事務產生的日誌中最後一條日誌的LSN)。 2)髒頁表:當前全部未刷到磁盤上得Page的集合(包括未提交事務和已提交事務),recoveryLSN是致使該頁爲髒頁的最先LSN(最近一次刷盤後最先開始的事務產生的日誌的LSN)。
每次Fuzzy Checkpoint,就是把這兩個表的數據生成一個快照,造成一條checkponit日誌,記入Redo Log中。
下圖展現了事務的開始標誌(S表示Start transaction),結束標誌(C表示Commit)以及Checkpoint在Redo Log中的位置(這裏只展現了活躍事務表,髒頁表也是相似的,惟一不一樣的就是髒頁集合只會增長,不會減小,髒頁集合中可能有些頁是乾淨的,但因爲Redo Log是冪等的,因此不影響)。
階段1中已經準備好了髒頁集合,取集合中髒頁的recoveryLSN的最小值(也就是最先開始髒的那一頁),獲得firstLSN,在Redo Log中從firstLSN開始遍歷到末尾,把每條Redo Log對應的Page所有從新刷到磁盤中。可是這些髒頁中可能有些頁並非髒的,因此這裏要作冪等。也就是利用Page中的一個PageLSN字段(它記錄了當前Page刷盤時最後一次修改它的日誌對應的LSN),在Redo重放的時候,判斷若是日誌的LSN比磁盤中得PageLSN要小,那就直接略過(這點很是相似TCP的超時重發中的判重機制)。在Redo完成後,保證了全部的髒頁都刷到了磁盤中,而且未提交事務也寫入了磁盤中,這時須要對未提交事務進行回滾,也就是階段3。
階段1中已經準備好了未提交事務集合,從最後一條日誌逆向遍歷(每條日誌都有一個preLSN字段,指向前一條日誌),直到未提交事務中的第一條日誌。
從後往前開始回滾,每遇到一條屬於未提交事務集合中事務的日誌,就生成一條對應的逆向SQL(這裏須要用到對應的歷史版本數據)執行,這條逆向SQL也會被記錄到Redo Log中,但與通常的日誌有所不一樣,稱爲Compensation Log Record(CLR),逆向執行完事務後(遇到事務的開始標誌)就提交,這也就完成了回滾。
上面在宕機回滾中,提到了生成逆向SQL,這個是須要使用到歷史版本數據的。Undo Log就是用於記錄和維護歷史版本數據的(事務的每一次修改,就是一個版本)。其實這是用到了CopyOnWrite的思想,每次事務在修改記錄以前,都會把該記錄拷貝一份出來(將它備份在Undo Log中)再進行修改操做(這個思想相似JDK中CopyOnWriteArratList)。事務的RC、RR隔離級別就是經過CopyOnWrite實現的。
併發的事務,要同時讀寫同一行數據,只能讀取數據的歷史版本,而不能讀取當前正在被修改的數據(因此這樣就有了丟失更新的問題,固然這個能夠經過加鎖等方式解決)。這種機制稱爲Multiversion concurrency control 多版本併發控制(MVCC)。基於MVCC的這種特性,一般select語句都是不加鎖的,由於他們讀到的都是歷史版本的數據,這種讀,叫作「快照讀」。
Undo Log並非log,而是數據(因此Undo Log也會被記錄到Redo Log中,在宕機後用Redo Log來恢復Undo Log),它記錄的不是事務執行的日誌,而是數據的歷史版本。一旦事務提交後,就不須要Undo Log了(它只在事務提交過程當中有用)。
因此Undo Log應該叫作記錄的備份數據,也就是在事務提交以前的備份數據(由於可能有其它事務還在引用歷史版本數據),事務提交後它就沒有用了。
Undo Log的結構除了主鍵ID和數據外,還有兩個字段。一個是修改該記錄的事務ID,一個是rollback_ptr(指向以前的一個版本,因此它用來串聯全部的歷史版本)。