事務
定義
事務是數據庫管理系統(DBMS)執行過程中的一個邏輯單位,由一個有限的數據庫操作序列構成。(維基百科)
MySQL只有InnoDB存儲引擎支持事務
事務的開啓和關閉
數據庫默認是自動事務開啓的,執行任意的增刪改都會自動開啓事務。
也可以手動開啓事務:
-- 設定事務是否自動開啓
set session autocommit = on/off;
-- 手工方式開啓
begin / start transaction
-- 結束事務:提交或回滾
commit / rollback
事務的四大特性(ACID)
原子性(Atomicity)
是指事務中對數據庫的一系列操作,要麼都是成功,要麼都是失敗。
在InnoDB裏面是通過undo log來實現的, 它記錄了數據修改之前的值 (邏輯日誌),一旦發生異常,就可以用undo log來實現回滾操作。
一致性(Consistency)
指的是數據庫的完整性約束沒有被破壞,事務執行的前後都是合法的數據狀態。
隔離性(Isolation)
隔離性是指在併發操作中,不同事務之間應該隔離開來,使每個併發中的事務不會相互干擾。
持久性(durability)
事務提交成功,事務中所有的數據操作都必須是永久性的,被持久化到數據庫中,即使提交事務後,數據庫馬上崩潰,在數據庫重啓時,也必須能保證通過某種機制恢復數據。
持久性是通過redo log和雙寫緩衝機制(double write buffer)保證的。
事務併發的問題
髒讀
事務裏面,一個事務查詢的數據,因爲其他事務修改了數據且並沒有提交,導致前後兩次讀取情況不一致。
重複讀
事務裏面,一個事務查詢的數據,因爲其他事務修改/刪除了數據並且提交了,導致前後兩次讀取情況不一致。
幻讀
事務裏面,一個事務查詢的數據,因爲其他事務插入了數據並且提交了,導致前後兩次讀取情況不一致。
事務的隔離級別
讀未提交 (Read Uncommitted)
一個事務可以讀取到其他事務未提交的數據,會出現髒讀。叫做RU,沒有解決任何的問題。
讀已提交 (Read Committed)
一個事務只能讀取到其他事務已提交的數據,不能讀取到其他事務未提交的數據,解決了髒讀的問題。
可重複讀 (Repeatable Read)
同一個事務裏面多次讀取同樣的數據結果是一樣的,對讀取的數據快照或者加鎖,解決了重複讀,但不能解決幻讀。
串行化 (Serializable)
所有的事務都是串行執行的,對數據的操作需要排隊,不存在事務的併發操作了,所以解決了所有的問題。
併發控制實現
LBCC
Lock Based Concurrency Control(基於鎖的併發控制):在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
MVCC
Multi Version Concurrency Control(多版本的併發控制):生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取。
核心思想:查詢當前事務之前提交的已存在的數據,即使後面被其他事務修改或者刪除了,當前事務查到的還是之前的數據。後面其他事務新增的數據,當前事務是看不到的。
InnoDB爲每行記錄都實現了三個隱藏字段
- ROWID:6字節,行標識。(主鍵索引有介紹)
- DB_TRX_ID:6字節,插入或更新行的最後一個事務的事務ID,自增的(創建版本號)
- DB_ROLL_PTR:7字節,回滾指針,數據被刪除或記錄爲舊數據的時候,記錄當前事務ID(刪除版本號)
MVCC的版本控制
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-h6yKOGS8-1590046122654)(http://assets.processon.com/chart_image/5ebcf2d17d9c08156c3febd0.png)]
MVCC的查找規則:只能查找創建時間小於等於當前事務ID的數據,和刪除時間大於當前事務ID的行(或未刪除)。
- 事務1 初始化插入了兩條user數據,此時創建版本是當前事務ID,刪除版本爲空。
- 事務2 查詢出來兩條user數據。
- 事務3 插入一條新數據,新數據創建版本是3,刪除版本爲空
- 事務2 查詢user表,根據規則,只能查到創建版本小於1的兩條數據
- 事務4 刪除user表第二條數據,該數據刪除版本爲4
- 事務2 查詢user表,根據規則,刪除版本4大於事務id2,可以被查出來。可以顯示兩條數據
- 事務5 更新user表第一條數據,原數據刪除版本爲5,新增一條創建版本爲5的版本數據,刪除版本爲空
- 事務2 查詢user表,根據規則,創建版本5大於事務id2,不展示,刪除版本5大於當前事務版本2可展示,所以展示原來的兩條數據。
通過版本號的控制,無論其他事務是插入、修改、刪除,第一個事務查詢到的數據都沒有變化。
InnoDB鎖機制
表鎖、行鎖
表鎖(Table Lock)
- 表級鎖一次會將整個表鎖定,顆粒度大。
- 加鎖效率高:獲取鎖和釋放鎖的速度快。
- 鎖衝突概率高,避免死鎖
- 併發性能差
行鎖(Record Lock)
- 記錄鎖,加鎖的是數據行,鎖顆粒度小
- 加鎖效率略低,加鎖邏輯複雜
- 鎖衝突概率小,會出現死鎖
- 併發性能高
行鎖表鎖對比
鎖定粒度: 表鎖 > 行鎖
加鎖效率: 表鎖 > 行鎖
衝突概率: 表鎖 > 行鎖
併發性能: 表鎖 < 行鎖
行鎖:共享鎖、排他鎖
共享鎖(Shared Locks)
定義
共享鎖,又稱爲讀鎖,簡稱S鎖。
共享鎖就是多個事務可以共享一把鎖,都能訪問到數據,但是能讀不能修改。
加鎖
select * from user where id=1 LOCK IN SHARE MODE;
釋放鎖
事務結束
排他鎖(Exclusive Locks)
定義
排他鎖,又稱爲寫鎖,簡稱X鎖。
排他鎖不能與其他鎖並存,一個事務獲取數據行的排他鎖,其他事務就不能再獲取該數據的鎖(共享鎖、排他鎖),只有該獲取了排他鎖的事務可以對數據行進行讀取和修改。
加鎖
自動:delete / update / insert 默認加上X鎖;
手動:select * from student where id=1 FOR UPDATE;
釋放鎖
事務結束
InnoDB行鎖實現
核心:行鎖鎖的是索引
InnoDB行鎖是通過給索引上的索引項加鎖來實現的,只有通過索引條件檢索數據,才使用行級鎖;在不通過索引條件查詢的時候,將使用表鎖。
意向鎖(Intention Lock)(表級)
意向鎖是由數據引擎自己維護的,用戶無法手動操作意向鎖 。
意向共享鎖(Intention Shared Lock,簡稱IS鎖)
表示事務準備給數據行加入共享鎖,也就是說一個數據行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(Intention Exclusive Lock,簡稱IX鎖)
表示事務準備給數據行加入排他鎖,說明事務在一個數據行加排他鎖前必須先取得該表的IX鎖。
意向鎖作用
表級和行級鎖共存時,意向鎖相當於一個表flag。
當該表被事務A加了行鎖,事務B又準備加表鎖,應該是阻塞的。這時候如果遍歷所有行判斷是否有鎖,效率低。直接判斷是否有意向鎖就能給出結果,效率高。
行鎖算法
記錄鎖(Record Lock)
記錄(Record)
索引中存在的主鍵值,叫做Record。
鎖
單個行記錄上的鎖,通過索引使用等值查詢,精準匹配到一條記錄的時候,這個時候使用的就是記錄鎖。
例:
-- 鎖住id 爲2
select * from user where id = 2 for update;
間隙鎖(Gap Lock)
間隙(Gap)
存在的Record隔開的數據,不存在的區間,叫做間隙(Gap),是一個左開右開的區間
鎖
查詢的記錄不存在,沒有命中任何一個record,無論是用等值查詢還是範圍查詢的時候,此時使用的是間隙鎖。
不會阻塞相同的間隙鎖,但是會阻塞插入間隙鎖,這也是用來防止幻讀的關鍵。
間隙鎖,只在RR隔離級別下存在。
例:
-- 返回查詢
select * from user where id >4 and id <7 for update;
-- 等值查詢沒命中
select * from user where id = 6 for update;
-- 阻塞插入 block
insert user id(5)
鎖定了間隙區間,阻塞插入。
臨鍵鎖(Next-key Lock)
臨鍵(Next-key)
間隙(Gap)連同右邊的記錄(Record),我們把它叫做臨鍵的區間,是一個左開右閉的區間。
鎖
使用範圍查詢時,命中Record記錄,還包含Gap間隙,就使用臨鍵鎖
是MySQL裏面默認的行鎖算法,相當於記錄鎖加上間隙鎖。
臨鍵鎖,鎖住最後一個key的下一個左開右閉的區間。
例:
-- 鎖住(4,7]和(7,10]
select * from t2 where id >5 and id <=7 for update;
-- 鎖住(7,10],(10,+∞)
select * from t2 where id >8 and id <=10 for update;