事務的基本要素(ACID)
- 原子性(Atomicity):事務開始後所有操作,要麼全部做完,要麼全部不做,不可能停滞在中間環節。事務執行過程中出錯,會復原到事務開始前的狀态,所有的操作就像沒有發生一樣。也就是說事務是一個不可分割的整體,就像化學中學過的原子,是物質構成的基本機關。
- 一緻性(Consistency):事務開始前和結束後,資料庫的完整性限制沒有被破壞 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
- 隔離性(Isolation):同一時間,隻允許一個事務請求同一資料,不同的事務之間彼此沒有任何幹擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。
- 持久性(Durability):事務完成後,事務對資料庫的所有更新将被儲存到資料庫,不能復原。
事務的并發問題
- 髒讀:事務A讀取了事務B更新的資料,然後B復原操作,那麼A讀取到的資料是髒資料
- 不可重複讀:事務 A 多次讀取同一資料,事務 B 在事務A多次讀取的過程中,對資料作了更新并送出,導緻事務A多次讀取同一資料時,結果 不一緻。
- 幻讀:系統管理者A将資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理者B就在這個時候插入了一條具體分數的記錄,當系統管理者A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。
小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重于修改,幻讀側重于新增或删除。解決不可重複讀的問題隻需鎖住滿足條件的行,解決幻讀需要鎖表
MySQL事務隔離級别
事務隔離級别 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
未送出讀(read-uncommitted)RUC | 是 | 是 | 是 |
已送出讀(read-committed)RC | 否 | 是 | 是 |
可重複讀(repeatable-read)RR | 否 | 否 | 是 |
可串行化(serializable)S | 否 | 否 | 否 |
- 送出讀(RC):隻能讀取到已經送出的資料。
- 可重複讀(RR):在同一個事務内的查詢都是事務開始時刻一緻的,InnoDB預設級别。
-
串行化(serializable)會鎖表,是以不會出現幻讀的情況,這種隔離級别并發性極低,
開發中很少會用到
- Mysql InnoDB 預設事務隔離級别為RR
InnoDB 鎖
鎖類型
- 讀鎖:也叫共享鎖、S鎖,若事務T對資料對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務隻能再對A加S鎖,而不能加X鎖,直到T釋放A上的S 鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
- 寫鎖:又稱排他鎖、X鎖。若事務T對資料對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。
- 表鎖:操作對象是資料表。Mysql大多數鎖政策都支援(常見mysql innodb),是系統開銷最低但并發性最低的一個鎖政策。事務t對整個表加讀鎖,則其他事務可讀不可寫,若加寫鎖,則其他事務增删改都不行。
- 行級鎖:操作對象是資料表中的一行。是MVCC技術用的比較多的,但在MYISAM用不了,行級鎖用mysql的儲存引擎實作而不是mysql伺服器。但行級鎖對系統開銷較大,處理高并發較好。
行鎖
InnoDB實作了以下兩種類型的行鎖
- 共享鎖(S):共享鎖就是多個事務對于同一資料可以共享一把鎖,都能通路到資料,但是隻能讀不能修改
- 排他鎖(X):排他鎖就是不能與其他所并存,如一個事務擷取了一個資料行的排他鎖,其他事務就不能再擷取該行的其他鎖,包括共享鎖和排他鎖,但是擷取排他鎖的事務是可以對資料就行讀取和修改
對于共享鎖大家可能很好了解,就是多個事務隻能讀資料不能改資料,
對于排他鎖大家的了解可能就有些差别,我當初就犯了一個錯誤,以為排他鎖鎖住一行資料後,
其他事務就不能讀取和修改該行資料,其實不是這樣的。排他鎖指的是一個事務在一行資料加上排他鎖後,
其他事務不能再在其上加其他的鎖。mysql InnoDB引擎預設的修改資料語句,
update,delete,insert
都會自動給涉及到的資料加上排他鎖,
select
語句預設不會加任何鎖類型,
如果加排他鎖可以使用
select ...for update
語句,加共享鎖可以使用
select ... lock in share mode
語句。是以加過排他鎖的資料行在其他事務種是不能修改資料的,
也不能通過
for update
和
lock in share mode
鎖的方式查詢資料,但可以直接通過
select ...from...
查詢資料,因為普通查詢沒有任何鎖機制。
表鎖(意向鎖)
為了允許行鎖和表鎖共存,實作多粒度鎖機制,InnoDB還有兩種内部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。
- 意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個資料行加共享鎖前必須先取得該表的IS鎖。
- 意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個資料行加排他鎖前必須先取得該表的IX鎖。
間隙鎖(Next-Key鎖)
為了解決幻讀問題,innodb引入了gap鎖。
當我們用範圍條件而不是相等條件檢索資料,并請求共享或排他鎖時,InnoDB會給符合條件的已有資料記錄的索引項加鎖;對于鍵值在條件範圍内但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
舉例來說,假如emp表中隻有101條記錄,其empid的值分别是 1,2,…,100,101,下面的SQL:
Select * from emp where empid > 100 for update;
是一個範圍條件的檢索,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大于101(這些記錄并不存在)的“間隙”加鎖。
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級别的要求,對于上面的例子,要是不使用間隙鎖,
如果其它事務插入了empid大于100的任何記錄,那麼本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是為了滿足其恢複和複制的需要。
有關其恢複和複制對鎖機制的影響,以及不同隔離級别下InnoDB使用間隙鎖的情況,
很顯然,在使用範圍條件檢索并鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍内鍵值的并發插入,
這往往會造成嚴重的鎖等待。是以,在實際應用開發中,尤其是并發插入比較多的應用,我們要盡量優化業務邏輯,盡量使用相等條件來通路更新資料,避免使用範圍條件。
還要特别說明的是,InnoDB除了通過範圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖!
InnoDB行鎖模式相容性清單
請求鎖模式 是否相容 目前鎖模式 | X | IX | S | IS |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 相容 | 沖突 | 相容 |
S | 沖突 | 沖突 | 相容 | 相容 |
IS | 沖突 | 相容 | 相容 | 相容 |
如果一個事務請求的鎖模式與目前的鎖相容,InnoDB就将請求的鎖授予該事務;反之,如果兩者不相容,該事務就要等待鎖釋放。
意向鎖是InnoDB自動加的,不需使用者幹預。對于UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X);對于普通SELECT語句,InnoDB不會加任何鎖;事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。
- 共享鎖(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
- 排他鎖(X):
SELECT * FROM table_name WHERE ... FOR UPDATE
用
SELECT ... IN SHARE MODE
獲得共享鎖,主要用在需要資料依存關系時來确認某行記錄是否存在,
并確定沒有人對這個記錄進行
UPDATE
或者
DELETE
操作。但是如果目前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,
對于鎖定行記錄後需要進行更新操作的應用,應該使用
SELECT... FOR UPDATE
方式獲得排他鎖。
InnoDB行鎖實作方式
InnoDB行鎖是通過給索引上的索引項加鎖來實作的,這一點MySQL與Oracle不同,後者是通過在資料塊中對相應資料行加鎖來實作的。
InnoDB這種行鎖實作特點意味着:隻有通過索引條件檢索資料,InnoDB才使用行級鎖,否則,InnoDB将使用表鎖!
在實際應用中,要特别注意InnoDB行鎖的這一特性,不然的話,可能導緻大量的鎖沖突,進而影響并發性能
InnoDB 的意向鎖有什麼作用
- 在mysql中有表鎖,
LOCK TABLE my_tabl_name READ; 用讀鎖鎖表,會阻塞其他事務修改表資料。
LOCK TABLE my_table_name WRITe; 用寫鎖鎖表,會阻塞其他事務讀和寫。`
- Innodb引擎又支援行鎖,行鎖分為
- 共享鎖,一個事務對一行的共享隻讀鎖。
- 排它鎖,一個事務對一行的排他讀寫鎖。
- 這兩中類型的鎖共存的問題
考慮這個例子:
事務A鎖住了表中的一行,讓這一行隻能讀,不能寫。之後,事務B申請整個表的寫鎖。如果事務B申請成功,那麼理論上它就能修改表中的任意一行,這與A持有的行鎖是沖突的。資料庫需要避免這種沖突,就是說要讓B的申請被阻塞,直到A釋放了行鎖。
資料庫要怎麼判斷這個沖突呢?
- step1:判斷表是否已被其他事務用表鎖鎖表
- step2:判斷表中的每一行是否已被行鎖鎖住。
注意step2,這樣的判斷方法效率實在不高,因為需要周遊整個表。
于是就有了意向鎖。
在意向鎖存在的情況下,事務A必須先申請表的意向共享鎖,成功後再申請一行的行鎖。
在意向鎖存在的情況下,上面的判斷可以改成
- step1:不變
- step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,是以,事務B申請表的寫鎖會被阻塞。
注意:申請意向鎖的動作是資料庫完成的,就是說,事務A申請一行的行鎖的時候,資料庫會自動先開始申請表的意向鎖,不需要我們程式員使用代碼來申請。
MVCC
MVCC (Multiversion Concurrency Control),即多版本并發控制技術,它使得大部分支援行鎖的事務引擎,
不再單純的使用行鎖來進行資料庫的并發控制,取而代之的是把資料庫的行鎖與行的多個版本結合起來,
隻需要很小的開銷,就可以實作非鎖定讀,進而大大提高資料庫系統的并發性能
關于innodb中MVCC的一些了解
補充
- 事務隔離級别為讀送出時,寫資料隻會鎖住相應的行
- 事務隔離級别為可重複讀時,如果檢索條件有索引(包括主鍵索引)的時候,預設加鎖方式是next-key 鎖;如果檢索條件沒有索引,更新資料時會鎖住整張表。一個間隙被事務加了鎖,其他事務是不能在這個間隙插入記錄的,這樣可以防止幻讀。
- 事務隔離級别為串行化時,讀寫資料都會鎖住整張表
- 隔離級别越高,越能保證資料的完整性和一緻性,但是對并發性能的影響也越大。
- MYSQL MVCC實作機制參考連結
- 關于next-key 鎖可以參考連結
- MySQL 加鎖處理分析