正文如下: 眾所周知,事務和鎖是mysql中非常重要功能,同時也是面試的重點和難點,本文會詳細介紹事務和鎖的相關概念及其實作原理,相信大家看完之后,一定會對事務和鎖有更加深入的理解,整理了一份328頁MySQL,PDF檔案
# 什么是事務
在維基百科中,對事務的定義是:事務是資料庫管理系統(DBMS)執行程序中的一個邏輯單位,由一個有限的資料庫操作序列構成,事務的四大特性
事務包含四大特性,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)(ACID),- 原子性(Atomicity) 原子性是指對資料庫的一系列操作,要么全部成功,要么全部失敗,不可能出現部分成功的情況,以轉賬場景為例,一個賬戶的余額減少,另一個賬戶的余額增加,這兩個操作一定是同時成功或者同時失敗的,
- 一致性(Consistency) 一致性是指資料庫的完整性約束沒有被破壞,在事務執行前后都是合法的資料狀態,這里的一致可以表示資料庫自身的約束沒有被破壞,比如某些欄位的唯一性約束、欄位長度約束等等;還可以表示各種實際場景下的業務約束,比如上面轉賬操作,一個賬戶減少的金額和另一個賬戶增加的金額一定是一樣的,
- 隔離性(Isolation) 隔離性指的是多個事務彼此之間是完全隔離、互不干擾的,隔離性的最終目的也是為了保證一致性,
- 持久性(Durability) 持久性是指只要事務提交成功,那么對資料庫做的修改就被永久保存下來了,不可能因為任何原因再回到原來的狀態,
事務的狀態
根據事務所處的不同階段,事務大致可以分為以下5個狀態:- 活動的(active) 當事務對應的資料庫操作正在執行程序中,則該事務處于活動狀態,
- 部分提交的(partially committed) 當事務中的最后一個操作執行完成,但還未將變更重繪到磁盤時,則該事務處于部分提交狀態,
- 失敗的(failed) 當事務處于活動或者部分提交狀態時,由于某些錯誤導致事務無法繼續執行,則事務處于失敗狀態,
- 中止的(aborted) 當事務處于失敗狀態,且回滾操作執行完畢,資料恢復到事務執行之前的狀態時,則該事務處于中止狀態,
- 提交的(committed) 當事務處于部分提交狀態,并且將修改過的資料都同步到磁盤之后,此時該事務處于提交狀態,
事務隔離級別
前面提到過,事務必須具有隔離性,實作隔離性最簡單的方式就是不允許事務并發,每個事務都排隊執行,但是這種方式性能實在太差了,為了兼顧事務的隔離性和性能,事務支持不同的隔離級別, 為了方便表述后續的內容,我們先建一張示例表hero,CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
# 事務并發執行遇到的問題
在事務并發執行時,如果不進行任何控制,可能會出現以下4類問題:- 臟寫(Dirty Write) 臟寫是指一個事務修改了其它事務未提交的資料,
- 臟讀(Dirty Read) 臟讀是指一個事務讀到了其它事務未提交的資料,
- 不可重復讀(Non-Repeatable Read) 不可重復讀指的是在一個事務執行程序中,讀取到其它事務已提交的資料,導致兩次讀取的結果不一致,
- 幻讀(Phantom) 幻讀是指的是在一個事務執行程序中,讀取到了其他事務新插入資料,導致兩次讀取的結果不一致,
不可重復讀和幻讀的區別在于不可重復讀是讀到的是其他事務修改或者洗掉的資料,而幻讀讀到的是其它事務新插入的資料,臟寫的問題太嚴重了,任何隔離級別都必須避免,其它無論是臟讀,不可重復讀,還是幻讀,它們都屬于資料庫的讀一致性的問題,都是在一個事務里面前后兩次讀取出現了不一致的情況,
# 四種隔離級別
在SQL標準中設立了4種隔離級別,用來解決上面的讀一致性問題,不同的隔離級別可以解決不同的讀一致性問題,- READ UNCOMMITTED:未提交讀,
- READ COMMITTED:已提交讀,
- REPEATABLE READ:可重復讀,
- SERIALIZABLE:串行化,
# MVCC
MVCC(Multi Version Concurrency Control),中文名是多版本并發控制,簡單來說就是通過維護資料歷史版本,從而解決并發訪問情況下的讀一致性問題,版本鏈
在InnoDB中,每行記錄實際上都包含了兩個隱藏欄位:事務id(trx_id)和回滾指標(roll_pointer),- trx_id:事務id,每次修改某行記錄時,都會把該事務的事務id賦值給trx_id隱藏列,
- roll_pointer:回滾指標,每次修改某行記錄時,都會把undo日志地址賦值給roll_pointer隱藏列,
ReadView
如果資料庫隔離級別是未提交讀(READ UNCOMMITTED),那么讀取版本鏈中最新版本的記錄即可,如果是是串行化(SERIALIZABLE),事務之間是加鎖執行的,不存在讀不一致的問題,但是如果是已提交讀(READ COMMITTED)或者可重復讀(REPEATABLE READ),就需要遍歷版本鏈中的每一條記錄,判斷該條記錄是否對當前事務可見,直到找到為止(遍歷完還沒找到就說明記錄不存在),InnoDB通過ReadView實作了這個功能,ReadView中主要包含以下4個內容:- m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id串列,
- min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值,
- max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值,
- creator_trx_id:表示生成該ReadView事務的事務id,
- 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問,
- 如果被訪問版本的trx_id屬性值小于ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問,
- 如果被訪問版本的trx_id屬性值大于或等于ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被當前事務訪問,
- 如果被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids串列中,如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問,
# 鎖
事務并發訪問同一資料資源的情況主要就分為讀-讀、寫-寫和讀-寫三種,- 讀-讀 即并發事務同時訪問同一行資料記錄,由于兩個事務都進行只讀操作,不會對記錄造成任何影響,因此并發讀完全允許,
- 寫-寫 即并發事務同時修改同一行資料記錄,這種情況下可能導致臟寫問題,這是任何情況下都不允許發生的,因此只能通過加鎖實作,也就是當一個事務需要對某行記錄進行修改時,首先會先給這條記錄加鎖,如果加鎖成功則繼續執行,否則就排隊等待,事務執行完成或回滾會自動釋放鎖,
- 讀-寫 即一個事務進行讀取操作,另一個進行寫入操作,這種情況下可能會產生臟讀、不可重復讀、幻讀,最好的方案是讀操作利用多版本并發控制(MVCC),寫操作進行加鎖,
鎖的粒度
按鎖作用的資料范圍進行分類的話,鎖可以分為行級鎖和表級鎖,- 行級鎖:作用在資料行上,鎖的粒度比較小,
- 表級鎖:作用在整張資料表上,鎖的粒度比較大,
鎖的分類
為了實作讀-讀之間不受影響,并且寫-寫、讀-寫之間能夠相互阻塞,Mysql使用了讀寫鎖的思路進行實作,具體來說就是分為了共享鎖和排它鎖: 1.共享鎖(Shared Locks):簡稱S鎖,在事務要讀取一條記錄時,需要先獲取該記錄的S鎖,S鎖可以在同一時刻被多個事務同時持有,我們可以用select ...... lock in share mode;的方式手工加上一把S鎖, 2.排他鎖(Exclusive Locks):簡稱X鎖,在事務要改動一條記錄時,需要先獲取該記錄的X鎖,X鎖在同一時刻最多只能被一個事務持有,X鎖的加鎖方式有兩種,第一種是自動加鎖,在對資料進行增刪改的時候,都會默認加上一個X鎖,還有一種是手工加鎖,我們用一個FOR UPDATE給一行資料加上一個X鎖, 還需要注意的一點是,如果一個事務已經持有了某行記錄的S鎖,另一個事務是無法為這行記錄加上X鎖的,反之亦然, 除了共享鎖(Shared Locks)和排他鎖(Exclusive Locks),Mysql還有意向鎖(Intention Locks),意向鎖是由資料庫自己維護的,一般來說,當我們給一行資料加上共享鎖之前,資料庫會自動在這張表上面加一個意向共享鎖(IS鎖);當我們給一行資料加上排他鎖之前,資料庫會自動在這張表上面加一個意向排他鎖(IX鎖),意向鎖可以認為是S鎖和X鎖在資料表上的標識,通過意向鎖可以快速判斷表中是否有記錄被上鎖,從而避免通過遍歷的方式來查看表中有沒有記錄被上鎖,提升加鎖效率,例如,我們要加表級別的X鎖,這時候資料表里面如果存在行級別的X鎖或者S鎖的,加鎖就會失敗,此時直接根據意向鎖就能知道這張表是否有行級別的X鎖或者S鎖,InnoDB中的表級鎖
InnoDB中的表級鎖主要包括表級別的意向共享鎖(IS鎖)和意向排他鎖(IX鎖)以及自增鎖(AUTO-INC鎖),其中IS鎖和IX鎖在前面已經介紹過了,這里不再贅述,我們接下來重點了解一下AUTO-INC鎖, 大家都知道,如果我們給某列欄位加了AUTO_INCREMENT自增屬性,插入的時候不需要為該欄位指定值,系統會自動保證遞增,系統實作這種自動給AUTO_INCREMENT修飾的列遞增賦值的原理主要是兩個: 1.AUTO-INC鎖:在執行插入陳述句的時先加上表級別的AUTO-INC鎖,插入執行完成后立即釋放鎖,如果我們的插入陳述句在執行前無法確定具體要插入多少條記錄,比如INSERT ... SELECT這種插入陳述句,一般采用AUTO-INC鎖的方式, 2.輕量級鎖:在插入陳述句生成AUTO_INCREMENT值時先才獲取這個輕量級鎖,然后在AUTO_INCREMENT值生成之后就釋放輕量級鎖,如果我們的插入陳述句在執行前就可以確定具體要插入多少條記錄,那么一般采用輕量級鎖的方式對AUTO_INCREMENT修飾的列進行賦值,這種方式可以避免鎖定表,可以提升插入性能,“mysql默認根據實際場景自動選擇加鎖方式,當然也可以通過innodb_autoinc_lock_mode強制指定只使用其中一種,”
InnoDB中的行級鎖
前面說過,通過MVCC可以解決臟讀、不可重復讀、幻讀這些讀一致性問題,但實際上這只是解決了普通select陳述句的資料讀取問題,事務利用MVCC進行的讀取操作稱之為快照讀,所有普通的SELECT陳述句在READ COMMITTED、REPEATABLE READ隔離級別下都算是快照讀,除了快照讀之外,還有一種是鎖定讀,即在讀取的時候給記錄加鎖,在鎖定讀的情況下依然要解決臟讀、不可重復讀、幻讀的問題,由于都是在記錄上加鎖,這些鎖都屬于行級鎖, InnoDB的行鎖,是通過鎖住索引來實作的,如果加鎖查詢的時候沒有使用過索引,會將整個聚簇索引都鎖住,相當于鎖表了,根據鎖定范圍的不同,行鎖可以使用記錄鎖(Record Locks)、間隙鎖(Gap Locks)和臨鍵鎖(Next-Key Locks)的方式實作,假設現在有一張表t,主鍵是id,我們插入了4行資料,主鍵值分別是 1、4、7、10,接下來我們就以聚簇索引為例,具體介紹三種形式的行鎖,- 記錄鎖(Record Locks) 所謂記錄,就是指聚簇索引中真實存放的資料,比如上面的1、4、7、10都是記錄,
- 間隙鎖(Gap Locks) 間隙指的是兩個記錄之間邏輯上尚未填入資料的部分,比如上述的(1,4)、(4,7)等,
- 臨鍵鎖(Next-Key Locks) 臨鍵指的是間隙加上它右邊的記錄組成的左開右閉區間,比如上述的(1,4]、(4,7]等,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/253404.html
標籤:其他
上一篇:【java自學】基礎知識點筆記
下一篇:Map
