摘要:事實上在數據庫引擎的實現中並不能實現徹底的事務隔離,好比串行化。
本文分享自華爲雲社區《【數據庫事務與鎖機制】- 事務隔離的實現》,原文做者:技術火炬手 。html
事實上在數據庫引擎的實現中並不能實現徹底的事務隔離,好比串行化。這種事務隔離方式雖然是比較理想的隔離措施,可是會對併發性能產生比較大的影響,因此在MySQL中事務的默認隔離級別是 REPEATABLE READS(可重複讀),下面咱們展開討論一下MySQL對數據庫隔離性的實現。mysql
MySQL 事務隔離性的實現
在MySQL InnoDB (下稱MySQL)中實現事務的隔離性是經過鎖實現的,你們知道在併發場景下我經常使用的隔離和一致性措施每每是經過鎖實現,因此鎖也是數據庫系統經常使用的一致性措施。算法
MySQL鎖的分類
咱們主要討論InnoDB 鎖的實現,可是也有必要簡單瞭解MySQL中其餘數據庫引擎對鎖的實現。總體來講MySQL 中能夠分爲三種鎖的類型 表鎖、行鎖、頁鎖,其中使用表鎖的是 MyISAM引擎,支持行鎖的是 InnoDB 引擎,同時InnoDB也支持表鎖,BDB 支持頁鎖(不是太瞭解)。sql
表鎖 table-level locking
表級別的鎖顧名思義就是加鎖的維度是表級別的,是給一個表上鎖,這種鎖的特色是 開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,可是併發度也是最低的,表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用。數據庫
MySQL 表鎖的使用
在MySQL中使用表鎖比較簡單,能夠經過 LOCK TABLE 語句對一張表進行加鎖,以下:session
# 加鎖 LOCK TABLE T_XXXXXXXXX; # 解鎖 UNLOCK TABLES;
加鎖和解鎖的語法架構
LOCK TABLES tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... lock_type: { READ [LOCAL] | [LOW_PRIORITY] WRITE } UNLOCK TABLES
須要注意的是 LOCK TABLE 是指當前會話的鎖,也就是經過 LOCK TABLE 顯示的爲當前會話獲取表鎖,做用是防止其餘會話在須要互斥訪問時修改表的數據,會話只能爲其自身獲取或釋放鎖。一個會話沒法獲取另外一會話的鎖,也不能釋放另外一會話持有的鎖。同時 LOCK TABLE 不僅僅能夠獲取一個表的鎖,也能夠是一個視圖,對於視圖鎖定,LOCK TABLES將視圖中使用的全部基本表添加到要鎖定的表集合中,並自動鎖定它們。併發
LOCK TABLES 在獲取新鎖以前,隱式釋放當前會話持有的全部表鎖
UNLOCK TABLES顯式釋放當前會話持有的全部表鎖
LOCK TABLE 語句有兩個比較重要的參數 lock_type 它能夠允許你指定加鎖的模式,是讀鎖仍是寫鎖,也就是 READ LOCK 和 WRITE LOCK。性能
- READ 鎖
讀鎖的特色是 持有鎖的會話能夠讀取表但不能寫入表,多個會話能夠同時獲取READ該表的鎖 - WRITE 鎖
持有鎖的會話能夠讀取和寫入表,只有持有鎖的會話才能訪問該表。在釋放鎖以前,沒有其餘會話能夠訪問它,保持鎖定狀態時,其餘會話對錶的鎖定請求將阻塞
WRITE鎖一般比READ鎖具備更高的優先級,以確保儘快處理更新。這意味着,若是一個會話獲取了一個READ鎖,而後另外一個會話請求了一個WRITE鎖,則隨後的 READ鎖請求將一直等待,直到請求該WRITE鎖的會話已獲取並釋放了該鎖
經過上面對錶鎖的簡單介紹咱們引出兩個比較重要的信息,就是讀鎖和寫鎖,那麼答案就浮出水面,在表級別的鎖中其實MySQL是經過 共享讀鎖,和排他寫鎖來實現隔離性的,下面咱們減小共享讀鎖和排他寫鎖。優化
共享讀鎖(Table Read Lock)
共享鎖又稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改
對MyISAM表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;也即當一個session給表加讀鎖,其餘session也能夠繼續讀取該表,但全部更新、刪除和插入將會阻塞,直到將表解鎖。MyISAM引擎在執行select時會自動給相關表加讀鎖,在執行update、delete和insert時會自動給相關表加寫鎖
獨佔寫鎖(Table Write Lock)
排他鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其餘所並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲取該行的其餘鎖,包括共享鎖和排他鎖,可是獲取排他鎖的事務是能夠對數據就行讀取和修改
獨佔寫鎖也被稱之爲排他寫鎖,MyISAM表的寫操做,則會阻塞其餘用戶對同一表的讀和寫操做;MyISAM表的讀操做與寫操做之間,以及寫操做之間是串行的。也即當一個session給表加寫鎖,其餘session全部讀取、更新、刪除和插入將會阻塞,直到將表解鎖
共享鎖和獨佔鎖的兼容性
行鎖 Row -level locking
在MySQL中 支持行鎖的引擎是InnoDB,因此咱們這裏咱們指的行鎖主要是說InnoDB的行鎖。
InnoDB鎖的實現和Oracle很是相似,提供一致性的非鎖定讀、行級鎖支持。行級鎖沒有相關額外的開銷,並能夠同時獲得併發性和一致性。
lock與latch
Latch通常稱爲閂鎖(輕量級的鎖),由於其要求鎖定的時間必須很是短。若持續的時間長,則應用的性能會很是差。在InnoDB中,latch又能夠分爲mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證併發線程操做臨界資源的正確性,而且一般沒有死鎖檢測的機制。
Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。而且通常lock的對象僅在事務commit或rollback後進行釋放(不一樣事務隔離級別釋放的時間可能不一樣)。
lock與latch的比較
latch能夠經過命令SHOW ENGINE INNODB MUTEX查看,Lock能夠經過命令SHOW ENGINE INNODB STATUS及information_schema架構下的表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS來查看 見如何解決長時間鎖等待
和上面表鎖中講的同樣 MySQL 行鎖也是經過 共享鎖和獨佔鎖(排他鎖)實現的,因此關於這兩種鎖的概述就不過多簡紹。
InnoDB還支持多粒度(granular)鎖定,容許事務同時存在行級鎖和表級鎖,這種種額外的鎖方式,稱爲意向鎖(Intention Lock)。意向鎖是將鎖定的對象分爲多個層次,意向鎖意味着事務但願在更細粒度(fine granularity)上進行加鎖
若是對最下層(最細粒度)的對象上鎖,那麼首先須要對粗粒度的對象上鎖,意向鎖爲表級鎖,不會阻塞除全表掃描之外的任何請求。設計目的主要是爲了在一個事務中揭示下一行將被請求的鎖類型。兩種意向鎖。
- 意向共享鎖(IS Lock),事務想要得到一張表中某幾行的共享鎖
- 意向排他鎖(IX Lock),事務想要得到一張表中某幾行的排他鎖
表級意向鎖與行級鎖的兼容性
下面命令或表均可以查看當前鎖的請求
SHOW FULL PROCESSLIST; SHOW ENGINE INNODB STATUS; SELECT * FROM information_schema.INNODB_TRX; SELECT * FROM information_schema.INNODB_LOCKS; SELECT * FROM information_schema.INNODB_LOCK_WAITS;
一致性非鎖定讀
一致性的非鎖定讀(consistent nonlocking read)是指InnoDB經過行多版本控制(multi versioning)的方式來讀取當前執行時間數據庫中行的數據。若是讀取的行正在執行DELETE或UPDATE操做,這時不會去等待行上鎖的釋放。而是去讀取行的一個快照數據(以前版本的數據)。
一個行記錄多個快照數據,通常稱這種技術爲行多版本技術。由此帶來的併發控制,稱之爲多版本併發控制(Multi Version Concurrency Control,MVCC)。
之因此稱爲非鎖定讀,由於不須要等待訪問的行上X鎖的釋放。實現方式是經過undo段來完成。而undo用來在事務中回滾數據,快照數據自己沒有額外的開銷,也不須要上鎖,由於沒有事務會對歷史數據進行修改操做。非鎖定讀機制極大地提升了數據庫的併發性。在不一樣事務隔離級別下,讀取的方式不一樣,並非在每一個事務隔離級別下都是採用非鎖定的一致性讀。此外,即便都是使用非鎖定的一致性讀,可是對於快照數據的定義也不相同。在事務隔離級別READ COMMITTED和REPEATABLE READ下,InnoDB使用非鎖定的一致性讀。但對快照數據的定義不相同。在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀老是讀取被鎖定行的最新一份快照數據。而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀老是讀取事務開始時的行數據版本。
自增加與鎖
自增加在數據庫中是很是常見的一種屬性,也是首選的主鍵方式。在InnoDB的內存結構中,對每一個含有自增加值的表都有一個自增加計數器(auto-increment counter)。
插入操做會依據這個自增加的計數器值加1賦予自增加列。這個實現方式稱作AUTO-INC Locking,採用了一種特殊的表鎖機制,爲了提升插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增加值插入的SQL語句後當即釋放。
所以InnoDB提供了一種輕量級互斥量的自增加實現機制,大大提升了自增加值插入的性能。同時提供了一個參數innodb_autoinc_lock_mode來控制自增加的模式,該參數的默認值爲1。瞭解其實現以前,先對自增加的插入進行分類,以下表:
參數innodb_autoinc_lock_mode的說明
InnoDB中自增加的實現和MyISAM不一樣,MyISAM存儲引擎是表鎖設計,自增加不用考慮併發插入的問題。若是主從分別使用InnoDB和MyISAM時,必須考慮這種狀況。
另外,在InnoDB存中,自增加值的列必須是索引,同時必須是索引的第一個列。若是不是第一個列會拋出異常,而MyISAM沒有這個問題。
外鍵和鎖
外鍵主要用於引用完整性的約束檢查。InnoDB對於一個外鍵列,若是沒有顯式地對這個列加索引,會自動對其加一個索引,能夠避免表鎖。而Oracle不會自動添加索引,須要手動添加,可能會產生死鎖問題。
對於外鍵值的插入或更新,首先須要查詢(select)父表中的記錄。可是select父表操做不是使用一致性非鎖定讀,由於這會致使數據不一致的問題,所以這時使用的是SELECT…LOCK IN SHARE MODE方式,即主動對父表加一個S鎖。若是這時父表上已經加了X鎖,子表上的操做會被阻塞。以下表:
行鎖的3種算法
InnoDB有以下3種行鎖的算法
- Record Lock:單個行記錄上的鎖。總去鎖住索引記錄,若是表沒有設置任何索引,會使用隱式的主鍵來進行鎖定
- Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄自己
- Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,而且鎖定記錄自己。行的查詢採用這種鎖定算法
例如一個索引有10,11,13和20這四個值,那麼該索引可能被Next-Key Locking的區間爲
採用Next-Key Lock的鎖定技術稱爲Next-Key Locking。其設計的目的是爲了解決幻讀問題(Phantom Problem)。Next-Key Lock是謂詞鎖(predict lock)的一種改進。還有previous-key locking技術。一樣上述的索引十、十一、13和20,若採用previous-key locking技術,那麼鎖定的區間爲
當查詢的索引含有惟一屬性時,會對Next-Key Lock進行優化。對彙集索引,將其降級爲Record Lock。對輔助索引,將對下一個鍵值加上gap lock,即對下一個鍵值的範圍爲加鎖
Gap Lock的做用是爲了阻止多個事務將記錄插入到同一範圍內,而這會產生致使幻讀問題,用戶能夠經過如下兩種方式來顯式地關閉Gap Lock
- 將事務的隔離級別設置爲READ COMMITTED
- 將參數innodb_locks_unsafe_for_binlog設置爲1
上述設置破壞了事務的隔離性,而且對於replication,可能會致使主從數據的不一致。此外,從性能上來看,READ COMMITTED也不會優於默認的事務隔離級別READ REPEATABLE。
解決幻讀問題
幻讀問題是指在同一事務下,連續執行兩次一樣的範圍查詢操做,獲得的結果可能不一樣
Next-Key Locking的算法就是爲了不幻讀問題。對於上述的SQL語句,其鎖住的不是單個值,而是對(2,+∞)這個範圍加了X鎖。所以任何對於這個範圍的插入不容許,從而避免了幻讀問題。Next-Key Locking機制在應用層還能夠實現惟一性的檢查。例如:
select * from table_name where col = xxx LOCK IN SHARE MODE;
若是用戶經過索引查詢一個值,並對該行加上一個SLock,那麼即便查詢的值不在,其鎖定的也是一個範圍,所以若沒有返回任何行,那麼新插入的值必定是惟一的。若是此時有多個事務併發操做,那麼這種惟一性檢查機制也不會存在問題。由於這時會致使死鎖,只有一個事務的插入操做會成功,而其他的事務會拋出死鎖的錯誤。
經過Next-Key Locking實現應用程序的惟一性檢查:
總結
以上咱們簡單簡紹了MySQL 如何經過鎖機制實現對事務的隔離,也簡紹了一些實現這些所的算法,若是對細節比較感興趣的同窗能夠參考 官方文檔 中對InnoDB 的詳細簡紹。