資料庫系統原理學習
一、事務
概念
事務指的是滿足 ACID 特性的一組操作,可以通過 Commit 提交一個事務,也可以使用 Rollback 進行回滾。
ACID
1. 原子性(Atomicity)
事務被視為不可分割的最小單元,事務的所有操作要麼全部提交成功,要麼全部失敗回滾。
回滾可以用回滾日誌來實現,回滾日誌記錄著事務所執行的修改操作,在回滾時反向執行這些修改操作即可。
2. 一致性(Consistency)
資料庫在事務執行前後都保持一致性狀態。在一致性狀態下,所有事務對一個數據的讀取結果都是相同的。
3. 隔離性(Isolation)
一個事務所做的修改在最終提交以前,對其它事務是不可見的。
4. 永續性(Durability)
一旦事務提交,則其所做的修改將會永遠儲存到資料庫中。即使系統發生崩潰,事務執行的結果也不能丟失。
使用重做日誌來保證永續性。
事務的 ACID 特性概念簡單,但不是很好理解,主要是因為這幾個特性不是一種平級關係:
- 只有滿足一致性,事務的執行結果才是正確的。
- 在無併發的情況下,事務序列執行,隔離性一定能夠滿足。此時只要能滿足原子性,就一定能滿足一致性。
- 在併發的情況下,多個事務並行執行,事務不僅要滿足原子性,還需要滿足隔離性,才能滿足一致性。
- 事務滿足持久化是為了能應對資料庫崩潰的情況。
AUTOCOMMIT
MySQL 預設採用自動提交模式。也就是說,如果不顯式使用START TRANSACTION
語句來開始一個事務,那麼每個查詢都會被當做一個事務自動提交。
二、併發一致性問題
在併發環境下,事務的隔離性很難保證,因此會出現很多併發一致性問題。
丟失修改
T1 和 T2 兩個事務都對一個數據進行修改,T1 先修改,T2 隨後修改,T2 的修改覆蓋了 T1 的修改。
讀髒資料
T1 修改一個數據,T2 隨後讀取這個資料。如果 T1 撤銷了這次修改,那麼 T2 讀取的資料是髒資料。
不可重複讀
T2 讀取一個數據,T1 對該資料做了修改 。如果 T2 再次讀取這個資料,此時讀取的結果和第一次讀取的結果不同。
幻影讀
T1 讀取某個範圍的資料,T2 在這個範圍內插入新的資料 ,T1 再次讀取這個範圍的資料,此時讀取的結果和和第一次讀取的結果不同。
產生併發不一致性問題主要原因是破壞了事務的隔離性,解決方法是通過併發控制來保證隔離性。併發控制可以通過封鎖來實現,但是封鎖操作需要使用者自己控制,相當複雜。資料庫管理系統提供了事務的隔離級別,讓使用者以一種更輕鬆的方式處理併發一致性問題。
三、封鎖
封鎖粒度
MySQL 中提供了兩種封鎖粒度:行級鎖以及表級鎖。
應該儘量只鎖定需要修改的那部分資料,而不是所有的資源。鎖定的資料量越少,發生鎖爭用的可能就越小,系統的併發程度就越高。
但是加鎖需要消耗資源,鎖的各種操作(包括獲取鎖、釋放鎖、以及檢查鎖狀態)都會增加系統開銷。因此封鎖粒度越小,系統開銷就越大。
在選擇封鎖粒度時,需要在鎖開銷和併發程度之間做一個權衡。
封鎖型別
1. 讀寫鎖
- 排它鎖(Exclusive),簡寫為 X 鎖,又稱寫鎖。
- 共享鎖(Shared),簡寫為 S 鎖,又稱讀鎖。
有以下兩個規定:
- 一個事務對資料物件 A 加了 X 鎖,就可以對 A 進行讀取和更新。加鎖期間其它事務不能對 A 加任何鎖。
- 一個事務對資料物件 A 加了 S 鎖,可以對 A 進行讀取操作,但是不能進行更新操作。加鎖期間其它事務能對 A 加 S 鎖,但是不能加 X 鎖。
鎖的相容關係如下:
- | X | S |
---|---|---|
X | × | × |
S | × | √ |
2. 意向鎖
使用意向鎖(Intention Locks)可以更容易地支援多粒度封鎖。
在存在行級鎖和表級鎖的情況下,事務 T 想要對錶 A 加 X 鎖,就需要先檢測是否有其它事務對錶 A 或者表 A 中的任意一行加了鎖,那麼就需要對錶 A 的每一行都檢測一次,這是非常耗時的。
意向鎖在原來的 X/S 鎖之上引入了 IX/IS,IX/IS 都是表鎖,用來表示一個事務想要在表中的某個資料行上加 X 鎖或 S 鎖。有以下兩個規定:
- 一個事務在獲得某個資料行物件的 S 鎖之前,必須先獲得表的 IS 鎖或者更強的鎖;
- 一個事務在獲得某個資料行物件的 X 鎖之前,必須先獲得表的 IX 鎖。
通過引入意向鎖,事務 T 想要對錶 A 加 X 鎖,只需要先檢測是否有其它事務對錶 A 加了 X/IX/S/IS 鎖,如果加了就表示有其它事務正在使用這個表或者表中某一行的鎖,因此事務 T 加 X 鎖失敗。
各種鎖的相容關係如下:
- | X | IX | S | IS |
---|---|---|---|---|
X | × | × | × | × |
IX | × | √ | × | √ |
S | × | × | √ | √ |
IS | × | √ | √ | √ |
解釋如下:
- 任意 IS/IX 鎖之間都是相容的,因為它們只是表示想要對錶加鎖,而不是真正加鎖;
- S 鎖只與 S 鎖和 IS 鎖相容,也就是說事務 T 想要對資料行加 S 鎖,其它事務可以已經獲得對錶或者表中的行的 S 鎖。
封鎖協議
1. 三級封鎖協議
一級封鎖協議
事務 T 要修改資料 A 時必須加 X 鎖,直到 T 結束才釋放鎖。
可以解決丟失修改問題,因為不能同時有兩個事務對同一個資料進行修改,那麼事務的修改就不會被覆蓋。
T1 | T2 |
---|---|
lock-x(A) | |
read A=20 | |
lock-x(A) | |
wait | |
write A=19 | . |
commit | . |
unlock-x(A) | . |
obtain | |
read A=19 | |
write A=21 | |
commit | |
unlock-x(A) |
二級封鎖協議
在一級的基礎上,要求讀取資料 A 時必須加 S 鎖,讀取完馬上釋放 S 鎖。
可以解決讀髒資料問題,因為如果一個事務在對資料 A 進行修改,根據 1 級封鎖協議,會加 X 鎖,那麼就不能再加 S 鎖了,也就是不會讀入資料。
T1 | T2 |
---|---|
lock-x(A) | |
read A=20 | |
write A=19 | |
lock-s(A) | |
wait | |
rollback | . |
A=20 | . |
unlock-x(A) | . |
obtain | |
read A=20 | |
commit | |
unlock-s(A) |
三級封鎖協議
在二級的基礎上,要求讀取資料 A 時必須加 S 鎖,直到事務結束了才能釋放 S 鎖。
可以解決不可重複讀的問題,因為讀 A 時,其它事務不能對 A 加 X 鎖,從而避免了在讀的期間資料發生改變。
T1 | T2 |
---|---|
lock-s(A) | |
read A=20 | |
lock-x(A) | |
wait | |
read A=20 | . |
commit | . |
unlock-s(A) | . |
obtain | |
read A=20 | |
write A=19 | |
commit | |
unlock-X(A) |
2. 兩段鎖協議
加鎖和解鎖分為兩個階段進行。
可序列化排程是指,通過併發控制,使得併發執行的事務結果與某個序列執行的事務結果相同。
事務遵循兩段鎖協議是保證可序列化排程的充分條件。例如以下操作滿足兩段鎖協議,它是可序列化排程。
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 隱式與顯示鎖定
MySQL 的 InnoDB 儲存引擎採用兩段鎖協議,會根據隔離級別在需要的時候自動加鎖,並且所有的鎖都是在同一時刻被釋放,這被稱為隱式鎖定。
InnoDB 也可以使用特定的語句進行顯示鎖定:
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
四、隔離級別
未提交讀(READ UNCOMMITTED)
事務中的修改,即使沒有提交,對其它事務也是可見的。
提交讀(READ COMMITTED)
一個事務只能讀取已經提交的事務所做的修改。換句話說,一個事務所做的修改在提交之前對其它事務是不可見的。
可重複讀(REPEATABLE READ)
保證在同一個事務中多次讀取同樣資料的結果是一樣的。
可序列化(SERIALIZABLE)
強制事務序列執行。
√:可能出現 ×:不可能出現 加鎖讀這個一列沒理解
隔離級別 | 髒讀 | 不可重複讀 | 幻影讀 | 加鎖讀 |
---|---|---|---|---|
未提交讀 | √ | √ | √ | × |
提交讀 | × | √ | √ | × |
可重複讀 | × | × | √ | × |
可序列化 | × | × | × | √ |
五、多版本併發控制
多版本併發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 儲存引擎實現隔離級別的一種具體方式,用於實現提交讀和可重複讀這兩種隔離級別。而未提交讀隔離級別總是讀取最新的資料行,無需使用 MVCC。可序列化隔離級別需要對所有讀取的行都加鎖,單純使用 MVCC 無法實現。
版本號
- 系統版本號:是一個遞增的數字,每開始一個新的事務,系統版本號就會自動遞增。
- 事務版本號:事務開始時的系統版本號。
隱藏的列
MVCC 在每行記錄後面都儲存著兩個隱藏的列,用來儲存兩個版本號:
- 建立版本號:指示建立一個數據行的快照時的系統版本號;
- 刪除版本號:如果該快照的刪除版本號大於當前事務版本號表示該快照有效,否則表示該快照已經被刪除了。
Undo 日誌
MVCC 使用到的快照儲存在 Undo 日誌中,該日誌通過回滾指標把一個數據行(Record)的所有快照連線起來。
實現過程
以下實現過程針對可重複讀隔離級別。
當開始新一個事務時,該事務的版本號肯定會大於當前所有資料行快照的建立版本號,理解這一點很關鍵。
1. SELECT
多個事務必須讀取到同一個資料行的快照,並且這個快照是距離現在最近的一個有效快照。但是也有例外,如果有一個事務正在修改該資料行,那麼它可以讀取事務本身所做的修改,而不用和其它事務的讀取結果一致。
把沒有對一個數據行做修改的事務稱為 T,T 所要讀取的資料行快照的建立版本號必須小於 T 的版本號,因為如果大於或者等於 T 的版本號,那麼表示該資料行快照是其它事務的最新修改,因此不能去讀取它。除此之外,T 所要讀取的資料行快照的刪除版本號必須大於 T 的版本號,因為如果小於等於 T 的版本號,那麼表示該資料行快照是已經被刪除的,不應該去讀取它。
2. INSERT
將當前系統版本號作為資料行快照的建立版本號。
3. DELETE
將當前系統版本號作為資料行快照的刪除版本號。
4. UPDATE
將當前系統版本號作為更新前的資料行快照的刪除版本號,並將當前系統版本號作為更新後的資料行快照的建立版本號。可以理解為先執行 DELETE 後執行 INSERT。
快照讀與當前讀
1. 快照讀
使用 MVCC 讀取的是快照中的資料,這樣可以減少加鎖所帶來的開銷。
select * from table ...;
2. 當前讀
讀取的是最新的資料,需要加鎖。以下第一個語句需要加 S 鎖,其它都需要加 X 鎖。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;
六、Next-Key Locks
Next-Key Locks 是 MySQL 的 InnoDB 儲存引擎的一種鎖實現。
MVCC 不能解決幻讀的問題,Next-Key Locks 就是為了解決這個問題而存在的。在可重複讀(REPEATABLE READ)隔離級別下,使用 MVCC + Next-Key Locks 可以解決幻讀問題。
Record Locks
鎖定一個記錄上的索引,而不是記錄本身。
如果表沒有設定索引,InnoDB 會自動在主鍵上建立隱藏的聚簇索引,因此 Record Locks 依然可以使用。
Gap Locks
鎖定索引之間的間隙,但是不包含索引本身。例如當一個事務執行以下語句,其它事務就不能在 t.c 中插入 15。
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
Next-Key Locks
它是 Record Locks 和 Gap Locks 的結合,不僅鎖定一個記錄上的索引,也鎖定索引之間的間隙。例如一個索引包含以下值:10, 11, 13, and 20,那麼就需要鎖定以下區間:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
七、關係資料庫設計理論
函式依賴
記 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 是最低級別的正規化。
1. 第一正規化 (1NF)
屬性不可分。
2. 第二正規化 (2NF)
每個非主屬性完全函式依賴於鍵碼。
可以通過分解來滿足。
分解前
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} 為鍵碼,有如下函式依賴:
- Sno -> Sname, Sdept
- Sdept -> Mname
- Sno, Cname-> Grade
Grade 完全函式依賴於鍵碼,它沒有任何冗餘資料,每個學生的每門課都有特定的成績。
Sname, Sdept 和 Mname 都部分依賴於鍵碼,當一個學生選修了多門課時,這些資料就會出現多次,造成大量冗餘資料。
分解後
關係-1
Sno | Sname | Sdept | Mname |
---|---|---|---|
1 | 學生-1 | 學院-1 | 院長-1 |
2 | 學生-2 | 學院-2 | 院長-2 |
3 | 學生-3 | 學院-2 | 院長-2 |
有以下函式依賴:
- Sno -> Sname, Sdept
- Sdept -> Mname
關係-2
Sno | Cname | Grade |
---|---|---|
1 | 課程-1 | 90 |
2 | 課程-2 | 80 |
2 | 課程-1 | 100 |
3 | 課程-2 | 95 |
有以下函式依賴:
- Sno, Cname -> Grade
3. 第三正規化 (3NF)
非主屬性不傳遞函式依賴於鍵碼。
上面的 關係-1 中存在以下傳遞函式依賴:
- Sno -> Sdept -> Mname
可以進行以下分解:
關係-11
Sno | Sname | Sdept |
---|---|---|
1 | 學生-1 | 學院-1 |
2 | 學生-2 | 學院-2 |
3 | 學生-3 | 學院-2 |
關係-12
Sdept | Mname |
---|---|
學院-1 | 院長-1 |
學院-2 | 院長-2 |
八、ER 圖
Entity-Relationship,有三個組成部分:實體、屬性、聯絡。
用來進行關係型資料庫系統的概念設計。
實體的三種聯絡
包含一對一,一對多,多對多三種。
- 如果 A 到 B 是一對多關係,那麼畫個帶箭頭的線段指向 B;
- 如果是一對一,畫兩個帶箭頭的線段;
- 如果是多對多,畫兩個不帶箭頭的線段。
下圖的 Course 和 Student 是一對多的關係。
表示出現多次的關係
一個實體在聯絡出現幾次,就要用幾條線連線。
下圖表示一個課程的先修關係,先修關係出現兩個 Course 實體,第一個是先修課程,後一個是後修課程,因此需要用兩條線來表示這種關係。
聯絡的多向性
雖然老師可以開設多門課,並且可以教授多名學生,但是對於特定的學生和課程,只有一個老師教授,這就構成了一個三元聯絡。
一般只使用二元聯絡,可以把多元聯絡轉換為二元聯絡。
表示子類
用一個三角形和兩條線來連線類和子類,與子類有關的屬性和聯絡都連到子類上,而與父類和子類都有關的連到父類上。
參考資料
- AbrahamSilberschatz, HenryF.Korth, S.Sudarshan, 等. 資料庫系統概念 [M]. 機械工業出版社, 2006.
- 施瓦茨. 高效能 MYSQL(第3版)[M]. 電子工業出版社, 2013.
- 史嘉權. 資料庫系統概論[M]. 清華大學出版社有限公司, 2006.
- The InnoDB Storage Engine
- Transaction isolation levels
- Concurrency Control
- The Nightmare of Locking, Blocking and Isolation Levels!
- Database Normalization and Normal Forms with an Example
- The basics of the InnoDB undo logging and history system
- MySQL locking for the busy web developer
- 淺入淺出 MySQL 和 InnoDB
- Innodb 中的事務隔離級別和鎖的關係
轉載自:https://github.com/CyC2018/CS-Notes/blob/master/notes/資料庫系統原理.md