數據庫-數據庫系統原理mysql
落花人獨立,微雨燕雙飛。sql
簡介:數據庫-數據庫系統原理。數據庫
事務指的是知足 ACID 特性的一組操做,能夠經過 Commit 提交一個事務,也可使用 Rollback 進行回滾。併發
事務被視爲不可分割的最小單元,事務的全部操做要麼所有提交成功,要麼所有失敗回滾。數據庫設計
回滾能夠用回滾日誌(Undo Log)來實現,回滾日誌記錄着事務所執行的修改操做,在回滾時反向執行這些修改操做便可。ide
數據庫在事務執行先後都保持一致性狀態。在一致性狀態下,全部事務對同一個數據的讀取結果都是相同的。函數
一個事務所作的修改在最終提交之前,對其它事務是不可見的。atom
一旦事務提交,則其所作的修改將會永遠保存到數據庫中。即便系統發生崩潰,事務執行的結果也不能丟失。spa
系統發生崩潰能夠用重作日誌(Redo Log)進行恢復,從而實現持久性。與回滾日誌記錄數據的邏輯修改不一樣,重作日誌記錄的是數據頁的物理修改。設計
事務的 ACID 特性概念簡單,但不是很好理解,主要是由於這幾個特性不是一種平級關係:
MySQL 默認採用自動提交模式。也就是說,若是不顯式使用 START TRANSACTION
語句來開始一個事務,那麼每一個查詢操做都會被當作一個事務並自動提交。
在併發環境下,事務的隔離性很難保證,所以會出現不少併發一致性問題。
丟失修改指一個事務的更新操做被另一個事務的更新操做替換。通常在現實生活中常會遇到,例如:T1 和 T2 兩個事務都對一個數據進行修改,T1 先修改並提交生效,T2 隨後修改,T2 的修改覆蓋了 T1 的修改。
讀髒數據指在不一樣的事務下,當前事務能夠讀到另外事務未提交的數據。例如:T1 修改一個數據但未提交,T2 隨後讀取這個數據。若是 T1 撤銷了此次修改,那麼 T2 讀取的數據是髒數據。
不可重複讀指在一個事務內屢次讀取同一數據集合。在這一事務還未結束前,另外一事務也訪問了該同一數據集合並作了修改,因爲第二個事務的修改,第一次事務的兩次讀取的數據可能不一致。例如:T2 讀取一個數據,T1 對該數據作了修改。若是 T2 再次讀取這個數據,此時讀取的結果和第一次讀取的結果不一樣。
幻讀本質上也屬於不可重複讀的狀況,T1 讀取某個範圍的數據,T2 在這個範圍內插入新的數據,T1 再次讀取這個範圍的數據,此時讀取的結果和和第一次讀取的結果不一樣。
產生併發不一致性問題的主要緣由是破壞了事務的隔離性,解決方法是經過併發控制來保證隔離性。併發控制能夠經過封鎖來實現,可是封鎖操做須要用戶本身控制,至關複雜。數據庫管理系統提供了事務的隔離級別,讓用戶以一種更輕鬆的方式處理併發一致性問題。
MySQL 中提供了兩種封鎖粒度:行級鎖以及表級鎖。
應該儘可能只鎖定須要修改的那部分數據,而不是全部的資源。鎖定的數據量越少,發生鎖爭用的可能就越小,系統的併發程度就越高。
可是加鎖須要消耗資源,鎖的各類操做(包括獲取鎖、釋放鎖、以及檢查鎖狀態)都會增長系統開銷。所以封鎖粒度越小,系統開銷就越大。
在選擇封鎖粒度時,須要在鎖開銷和併發程度之間作一個權衡。
有如下兩個規定:
使用意向鎖(Intention Locks)能夠更容易地支持多粒度封鎖。
在存在行級鎖和表級鎖的狀況下,事務 T 想要對錶 A 加 X 鎖,就須要先檢測是否有其它事務對錶 A 或者表 A 中的任意一行加了鎖,那麼就須要對錶 A 的每一行都檢測一次,這是很是耗時的。
意向鎖在原來的 X/S 鎖之上引入了 IX/IS,IX/IS 都是表鎖,用來表示一個事務想要在表中的某個數據行上加 X 鎖或 S 鎖。有如下兩個規定:
經過引入意向鎖,事務 T 想要對錶 A 加 X 鎖,只須要先檢測是否有其它事務對錶 A 加了 X/IX/S/IS 鎖,若是加了就表示有其它事務正在使用這個表或者表中某一行的鎖,所以事務 T 加 X 鎖失敗。
一級封鎖協議
事務 T 要修改數據 A 時必須加 X 鎖,直到 T 結束才釋放鎖。
能夠解決丟失修改問題,由於不能同時有兩個事務對同一個數據進行修改,那麼事務的修改就不會被覆蓋。
二級封鎖協議
在一級的基礎上,要求讀取數據 A 時必須加 S 鎖,讀取完立刻釋放 S 鎖。
能夠解決讀髒數據問題,由於若是一個事務在對數據 A 進行修改,根據 1 級封鎖協議,會加 X 鎖,那麼就不能再加 S 鎖了,也就是不會讀入數據。
三級封鎖協議
在二級的基礎上,要求讀取數據 A 時必須加 S 鎖,直到事務結束了才能釋放 S 鎖。
能夠解決不可重複讀的問題,由於讀 A 時,其它事務不能對 A 加 X 鎖,從而避免了在讀的期間數據發生改變。
加鎖和解鎖分爲兩個階段進行。
可串行化調度是指,經過併發控制,使得併發執行的事務結果與某個串行執行的事務結果相同。串行執行的事務互不干擾,不會出現併發一致性問題。
事務遵循兩段鎖協議是保證可串行化調度的充分條件。例如如下操做知足兩段鎖協議,它是可串行化調度。
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
但不是必要條件,例如如下操做不知足兩段鎖協議,但它仍是可串行化調度。
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
MySQL 的 InnoDB 存儲引擎採用兩段鎖協議,會根據隔離級別在須要的時候自動加鎖,而且全部的鎖都是在同一時刻被釋放,這被稱爲隱式鎖定。
InnoDB 也可使用特定的語句進行顯示鎖定:
1 SELECT ... LOCK In SHARE MODE; 2 SELECT ... FOR UPDATE;
事務中的修改,即便沒有提交,對其它事務也是可見的。
一個事務只能讀取已經提交的事務所作的修改。換句話說,一個事務所作的修改在提交以前對其它事務是不可見的。
保證在同一個事務中屢次讀取同一數據的結果是同樣的,可重複讀爲 Mysql 默認隔離級別。
強制事務串行執行,這樣多個事務互不干擾,不會出現併發一致性問題。
該隔離級別須要加鎖實現,由於要使用加鎖機制保證同一時間只有一個事務執行,也就是保證事務串行執行。
多版本併發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存儲引擎實現隔離級別的一種具體方式,用於實現提交讀和可重複讀這兩種隔離級別。
提交讀隔離級別老是讀取最新的數據行,要求很低,無需使用 MVCC。
可串行化隔離級別須要對全部讀取的行都加鎖,單純使用 MVCC 沒法實現。
在封鎖一節中提到,加鎖能解決多個事務同時執行時出現的併發一致性問題。在實際場景中讀操做每每多於寫操做,所以又引入了讀寫鎖來避免沒必要要的加鎖操做,例如讀和讀沒有互斥關係。讀寫鎖中讀和寫操做仍然是互斥的,而 MVCC 利用了多版本的思想,寫操做更新最新的版本快照,而讀操做去讀舊版本快照,沒有互斥關係,這一點和 CopyOnWrite 相似。
在 MVCC 中事務的修改操做(DELETE、INSERT、UPDATE)會爲數據行新增一個版本快照。
髒讀和不可重複讀最根本的緣由是事務讀取到其它事務未提交的修改。在事務進行讀取操做時,爲了解決髒讀和不可重複讀問題,MVCC 規定只能讀取已經提交的快照。固然一個事務能夠讀取自身未提交的快照,這不算是髒讀。
MVCC 的多版本指的是多個版本的快照,快照存儲在 Undo 日誌中,該日誌經過回滾指針 ROLL_PTR 把一個數據行的全部快照鏈接起來。
例如在 MySQL 建立一個表 t,包含主鍵 id 和一個字段 x。咱們先插入一個數據行,而後對該數據行執行兩次更新操做。
1 INSERT INTO t(id, x) VALUES(1, "a"); 2 UPDATE t SET x="b" WHERE id=1; 3 UPDATE t SET x="c" WHERE id=1;
由於沒有使用 START TRANSACTION
將上面的操做當成一個事務來執行,根據 MySQL 的 AUTOCOMMIT 機制,每一個操做都會被當成一個事務來執行,因此上面的操做總共涉及到三個事務。快照中除了記錄事務版本號 TRX_ID 和操做以外,還記錄了一個 bit 的 DEL 字段,用於標記是否被刪除。
INSERT、UPDATE、DELETE 操做會建立一個日誌,並將事務版本號 TRX_ID 寫入。DELETE 能夠當作是一個特殊的 UPDATE,還會額外將 DEL 字段設置爲 1。
MVCC 維護了一個 ReadView 結構,主要包含了當前系統未提交的事務列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},還有該列表的最小值 TRX_ID_MIN 和 最大值 TRX_ID_MAX。
在進行 SELECT 操做時,根據數據行快照的 TRX_ID 與 TRX_ID_MIN 和 TRX_ID_MAX 之間的關係,從而判斷數據行快照是否可使用:
TRX_ID < TRX_ID_MIN,表示該數據行快照時在當前全部未提交事務以前進行更改的,所以可使用。
TRX_ID > TRX_ID_MAX,表示該數據行快照是在事務啓動以後被更改的,所以不可以使用。
TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX,須要根據隔離級別再進行判斷:
在數據行快照不可以使用的狀況下,須要沿着 Undo Log 的回滾指針 ROLL_PTR 找到下一個快照,再進行上面的判斷。
MVCC 的 SELECT 操做是快照中的數據,不須要進行加鎖操做。
SELECT * FROM table ...;
MVCC 其它會對數據庫進行修改的操做(INSERT、UPDATE、DELETE)須要進行加鎖操做,從而讀取最新的數據。能夠看到 MVCC 並非徹底不用加鎖,而只是避免了 SELECT 的加鎖操做。
1 INSERT; 2 UPDATE; 3 DELETE;
在進行 SELECT 操做時,能夠強制指定進行加鎖操做。如下第一個語句須要加 S 鎖,第二個須要加 X 鎖。
1 SELECT * FROM table WHERE ? lock in share mode; 2 SELECT * FROM table WHERE ? for update;
Next-Key Locks 是 MySQL 的 InnoDB 存儲引擎的一種鎖實現。
MVCC 不能解決幻影讀問題,Next-Key Locks 就是爲了解決這個問題而存在的。在可重複讀(REPEATABLE READ)隔離級別下,使用 MVCC + Next-Key Locks 能夠解決幻讀問題。
鎖定一個記錄上的索引,而不是記錄自己。
若是表沒有設置索引,InnoDB 會自動在主鍵上建立隱藏的聚簇索引,所以 Record Locks 依然可使用。
鎖定索引之間的間隙,可是不包含索引自己。例如當一個事務執行如下語句,其它事務就不能在 t.c 中插入 15。
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
它是 Record Locks 和 Gap Locks 的結合,不只鎖定一個記錄上的索引,也鎖定索引之間的間隙。它鎖定一個前開後閉區間,例如一個索引包含如下值:10, 11, 13, and 20,那麼就須要鎖定如下區間:
1 (-∞, 10] 2 (10, 11] 3 (11, 13] 4 (13, 20] 5 (20, +∞)
記 A->B 表示 A 函數決定 B,也能夠說 B 函數依賴於 A。
若是 {A1,A2,... ,An} 是關係的一個或多個屬性的集合,該集合函數決定了關係的其它全部屬性而且是最小的,那麼該集合就稱爲鍵碼。
對於 A->B,若是能找到 A 的真子集 A',使得 A'-> B,那麼 A->B 就是部分函數依賴,不然就是徹底函數依賴。
對於 A->B,B->C,則 A->C 是一個傳遞函數依賴。
如下的學生課程關係的函數依賴爲 {Sno, Cname} -> {Sname, Sdept, Mname, Grade},鍵碼爲 {Sno, Cname}。也就是說,肯定學生和課程以後,就能肯定其它信息。
Sno | Sname | Sdept | Mname | Cname | Grade |
---|---|---|---|---|---|
1 | 學生-1 | 學院-1 | 院長-1 | 課程-1 | 90 |
2 | 學生-2 | 學院-2 | 院長-2 | 課程-2 | 80 |
2 | 學生-2 | 學院-2 | 院長-2 | 課程-1 | 100 |
3 | 學生-3 | 學院-2 | 院長-2 | 課程-2 | 95 |
不符合範式的關係,會產生不少異常,主要有如下四種異常:
學生-2
出現了兩次。課程-1
須要刪除第一行和第三行,那麼 學生-1
的信息就會丟失。範式理論是爲了解決以上提到四種異常。
高級別範式的依賴於低級別的範式,1NF 是最低級別的範式。
屬性不可分,即要求數據庫表的每一列都是不可分割的原子數據項。
每一個非主屬性徹底函數依賴於鍵碼,即確保數據庫表中的每一列都和主鍵相關,而不能只與主鍵的某一部分相關。
能夠經過分解來知足。
分解前
Sno | Sname | Sdept | Mname | Cname | Grade |
---|---|---|---|---|---|
1 | 學生-1 | 學院-1 | 院長-1 | 課程-1 | 90 |
2 | 學生-2 | 學院-2 | 院長-2 | 課程-2 | 80 |
2 | 學生-2 | 學院-2 | 院長-2 | 課程-1 | 100 |
3 | 學生-3 | 學院-2 | 院長-2 | 課程-2 | 95 |
以上學生課程關係中,{Sno, Cname} 爲鍵碼,有以下函數依賴:
Grade 徹底函數依賴於鍵碼,它沒有任何冗餘數據,每一個學生的每門課都有特定的成績。
Sname, Sdept 和 Mname 都部分依賴於鍵碼,當一個學生選修了多門課時,這些數據就會出現屢次,形成大量冗餘數據。
分解後
關係-1
Sno | Sname | Sdept | Mname |
---|---|---|---|
1 | 學生-1 | 學院-1 | 院長-1 |
2 | 學生-2 | 學院-2 | 院長-2 |
3 | 學生-3 | 學院-2 | 院長-2 |
有如下函數依賴:
關係-2
Sno | Cname | Grade |
---|---|---|
1 | 課程-1 | 90 |
2 | 課程-2 | 80 |
2 | 課程-1 | 100 |
3 | 課程-2 | 95 |
有如下函數依賴:
非主屬性不傳遞函數依賴於鍵碼,即確保數據表中的每一列數據都和主鍵直接相關,而不能間接相關。
上面的 關係-1 中存在如下傳遞函數依賴:
能夠進行如下分解:
關係-11
Sno | Sname | Sdept |
---|---|---|
1 | 學生-1 | 學院-1 |
2 | 學生-2 | 學院-2 |
3 | 學生-3 | 學院-2 |
關係-12
Sdept | Mname |
---|---|
學院-1 | 院長-1 |
學院-2 | 院長-2 |
Entity-Relationship,有三個組成部分:實體、屬性、聯繫。
用來進行關係型數據庫系統的概念設計。
落花人獨立
微雨燕雙飛