事务(ACID)、并发一致性问题(丢失修改、读脏数据、不可重复读、幻影读)、封锁(封锁粒度、类型、协议、MySQL 隐式与显示锁定)
1. 事務
1.1 概念
事務指的是滿足 ACID 特性的一組操作,可以通過 Commit 提交一個事務,也可以使用 Rollback 進行回滾。
1.2?ACID
1.2.1? 原子性(Atomicity)
事務被視為不可分割的最小單元,事務的所有操作要么全部提交成功,要么全部失敗回滾。
回滾可以用日志來實現,日志記錄著事務所執行的修改操作,在回滾時反向執行這些修改操作即可。
1.2.2 一致性(Consistency)
數據庫在事務執行前后都保持一致性狀態。在一致性狀態下,所有事務對一個數據的讀取結果都是相同的。
1.2.3? 隔離性(Isolation)
一個事務所做的修改在最終提交以前,對其它事務是不可見的。
1.2.4 持久性(Durability)
一旦事務提交,則其所做的修改將會永遠保存到數據庫中。即使系統發生崩潰,事務執行的結果也不能丟失。
可以通過數據庫備份和恢復來實現,在系統發生崩潰時,使用備份的數據庫進行數據恢復。
事務的 ACID 特性概念簡單,但不是很好理解,主要是因為這幾個特性不是一種平級關系:
- 只有滿足一致性,事務的執行結果才是正確的。
- 在無并發的情況下,事務串行執行,隔離性一定能夠滿足。此時只要能滿足原子性,就一定能滿足一致性。
- 在并發的情況下,多個事務并行執行,事務不僅要滿足原子性,還需要滿足隔離性,才能滿足一致性。
- 事務滿足持久化是為了能應對數據庫崩潰的情況。
1.3?AUTOCOMMIT
MySQL 默認采用自動提交模式。也就是說,如果不顯式使用 START TRANSACTION 語句來開始一個事務,那么每個查詢都會被當做一個事務自動提交。
2. 并發一致性問題
在并發環境下,事務的隔離性很難保證,因此會出現很多并發一致性問題。
2.1 丟失修改
T1 和 T2 兩個事務都對一個數據進行修改,T1 先修改,T 2隨后修改,T 2的修改覆蓋了T1的修改。
2.2?讀臟數據
T1修改一個數據,T2隨后讀取這個數據。如果 T1撤銷了這次修改,那么 T2讀取的數據是臟數據。
2.3?不可重復讀
T2讀取一個數據,T1對該數據做了修改。如果 T2再次讀取這個數據,此時讀取的結果和第一次讀取的結果不同。
2.4 幻影讀
T1讀取某個范圍的數據,T2在這個范圍內插入新的數據,T1再次讀取這個范圍的數據,此時讀取的結果和和第一次讀取的結果不同。
產生并發不一致性問題主要原因是破壞了事務的隔離性,解決方法是通過并發控制來保證隔離性。
并發控制可以通過封鎖來實現,但是封鎖操作需要用戶自己控制,相當復雜。
數據庫管理系統提供了事務的隔離級別,讓用戶以一種更輕松的方式處理并發一致性問題。
3. 封鎖
3.1 封鎖粒度
MySQL 中提供了兩種封鎖粒度:行級鎖以及表級鎖。
- 應該盡量只鎖定需要修改的那部分數據,而不是所有的資源。
- 鎖定的數據量越少,發生鎖爭用的可能就越小,系統的并發程度就越高。
- 但是加鎖需要消耗資源,鎖的各種操作(包括獲取鎖、釋放鎖、以及檢查鎖狀態)都會增加系統開銷。
- 因此封鎖粒度越小,系統開銷就越大。
在選擇封鎖粒度時,需要在鎖開銷和并發程度之間做一個權衡。
3.2?封鎖類型
3.2.1 讀寫鎖
- 排它鎖(Exclusive) ,簡寫為 X 鎖,又稱寫鎖。
- 共享鎖(Shared) ,簡寫為 S 鎖,又稱讀鎖。
有以下兩個規定:
一個事務對數據對象 A 加了 X 鎖,就可以對 A 進行讀取和更新。加鎖期間其它事務不能對 A 加任何鎖。
一個事務對數據對象 A 加了 S 鎖,可以對 A 進行讀取操作,但是不能進行更新操作。加鎖期間其它事務能對 A 加 S 鎖,但是不能加 X 鎖。
鎖的兼容關系如下:
| × | × |
| × | √ |
3.2.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 鎖失敗。
各種鎖的兼容關系如下:
| × | × | × | × |
| × | √ | × | √ |
| × | × | √ | √ |
| × | √ | √ | √ |
解釋如下:
任意 IS/IX 鎖之間都是兼容的,因為它們只是表示想要對表加鎖,而不是真正加鎖;
S 鎖只與 S 鎖和 IS 鎖兼容,也就是說事務 T 想要對數據行加 S 鎖,其它事務可以已經獲得對表或者表中的行的 S 鎖。
3.3 封鎖協議
3.3.1 三級封鎖協議
一級封鎖協議
事務 T 要修改數據 A 時必須加 X 鎖,直到 T 結束才釋放鎖。
可以解決丟失修改問題,因為不能同時有兩個事務對同一個數據進行修改,那么事務的修改就不會被覆蓋。
二級封鎖協議
在一級的基礎上,要求讀取數據 A 時必須加 S 鎖,讀取完馬上釋放 S 鎖。
可以解決讀臟數據問題,因為如果一個事務在對數據 A 進行修改,根據 1 級封鎖協議,會加 X 鎖,那么就不能再加 S 鎖了,也就是不會讀入數據。
三級封鎖協議
在二級的基礎上,要求讀取數據 A 時必須加 S 鎖,直到事務結束了才能釋放 S鎖。
可以解決不可重復讀的問題,因為讀 A 時,其它事務不能對 A 加 X 鎖,從而避免了在讀的期間數據發生改變。
3.3.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)3.4?MySQL 隱式與顯示鎖定
MySQL 的 InnoDB 存儲引擎采用兩段鎖協議,會根據隔離級別在需要的時候自動加鎖,并且所有的鎖都是在同一時刻被釋放,這被稱為隱式鎖定。
InnoDB 也可以使用特定的語句進行顯示鎖定:
SELECT ... LOCK In SHARE MODE; SELECT ... FOR UPDATE;?
總結
以上是生活随笔為你收集整理的事务(ACID)、并发一致性问题(丢失修改、读脏数据、不可重复读、幻影读)、封锁(封锁粒度、类型、协议、MySQL 隐式与显示锁定)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 复制(主从复制、读写分离)
- 下一篇: 隔离级别(未提交读、提交读、可重复读、可