十、高性能基礎(chǔ)(二)MySQL 事務(wù)與鎖機(jī)制

1、事務(wù)及其特性

首先看看什么是事務(wù)?事務(wù)具有哪些特性?
簡單來說,事務(wù)是指作為單個邏輯工作單元執(zhí)行的一系列操作,這些操作要么全做,要么全不做,是一個不可分割的工作單元。

一個邏輯工作單元要成為事務(wù),在關(guān)系型數(shù)據(jù)庫管理系統(tǒng)中,必須滿足 4 個特性,即所謂的 ACID:原子性、一致性、隔離性和持久性。
ACID 及它們之間的關(guān)系如下圖所示,比如 4 個特性中有 3 個與 WAL 有關(guān)系,都需要通過 Redo、Undo 日志來保證等。

  • 一致性(consistency):事務(wù)開始之前和事務(wù)結(jié)束之后,數(shù)據(jù)庫的完整性限制未被破壞。

一致性其實(shí)包括兩部分內(nèi)容,分別是約束一致性和數(shù)據(jù)一致性。
(1)約束一致性:數(shù)據(jù)庫中創(chuàng)建表結(jié)構(gòu)時(shí)所指定的外鍵、Check、唯一索引等約束。可惜在 MySQL 中,是不支持 Check 的,只支持另外兩種,所以約束一致性就非常容易理解了。
(2)數(shù)據(jù)一致性:是一個綜合性的規(guī)定,或者說是一個把握全局的規(guī)定。它是由原子性、持久性、隔離性共同保證的結(jié)果,而不是單單依賴于某一種技術(shù)。

  • 原子性(atomicity):事務(wù)的所有操作,要么全部完成,要么全部不完成,不會結(jié)束在某個中間環(huán)節(jié)。

原子性也就是說用戶感受不到一個正在改的狀態(tài)。MySQL 是通過 WAL(Write Ahead Log)技術(shù)來實(shí)現(xiàn)這種效果的。
原子性和 WAL 到底有什么關(guān)系呢?其實(shí)關(guān)系非常大。舉例來講,如果事務(wù)提交了,那改了的數(shù)據(jù)就生效了,如果此時(shí) Buffer Pool 的臟頁沒有刷盤,如何來保證改了的數(shù)據(jù)生效呢?就需要使用 Redo 日志恢復(fù)出來的數(shù)據(jù)。而如果事務(wù)沒有提交,且 Buffer Pool 的臟頁被刷盤了,那這個本不應(yīng)該存在的數(shù)據(jù)如何消失呢?就需要通過 Undo 來實(shí)現(xiàn)了,Undo 又是通過 Redo 來保證的,所以最終原子性的保證還是靠 Redo 的 WAL 機(jī)制實(shí)現(xiàn)的。

  • 持久性(durability):事務(wù)完成之后,事務(wù)所做的修改進(jìn)行持久化保存,不會丟失。

所謂持久性,就是指一個事務(wù)一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應(yīng)該是永久性的,接下來的操作或故障不應(yīng)該對其有任何影響。前面已經(jīng)講到,事務(wù)的原子性可以保證一個事務(wù)要么全執(zhí)行,要么全不執(zhí)行的特性,這可以從邏輯上保證用戶看不到中間的狀態(tài)。但持久性是如何保證的呢?一旦事務(wù)提交,通過原子性,即便是遇到宕機(jī),也可以從邏輯上將數(shù)據(jù)找回來后再次寫入物理存儲空間,這樣就從邏輯和物理兩個方面保證了數(shù)據(jù)不會丟失,即保證了數(shù)據(jù)庫的持久性。

  • 隔離性(isolation):當(dāng)多個事務(wù)并發(fā)訪問數(shù)據(jù)庫中的同一數(shù)據(jù)時(shí),所表現(xiàn)出來的相互關(guān)系。

所謂隔離性,指的是一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾,即一個事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對其他的并發(fā)事務(wù)是隔離的。鎖和多版本控制就符合隔離性。
InnoDB 支持的隔離性有 4 種,隔離性從低到高分別為:讀未提交、讀提交、可重復(fù)讀、可串行化。
(1)讀未提交(RU,Read Uncommitted)。它能讀到一個事務(wù)的中間過程,違背了 ACID 特性,存在臟讀的問題,所以基本不會用到,可以忽略。
(2)讀提交(RC,Read Committed)。它表示如果其他事務(wù)已經(jīng)提交,那么我們就可以看到,這也是一種最普遍適用的級別。但由于一些歷史原因,可能 RC 在生產(chǎn)環(huán)境中用的并不多。
(3)可重復(fù)讀(RR,Repeatable Read),是目前被使用得最多的一種級別。其特點(diǎn)是有 Gap 鎖、目前還是默認(rèn)的級別、在這種級別下會經(jīng)常發(fā)生死鎖、低并發(fā)等問題。
(4)可串行化,這種實(shí)現(xiàn)方式,其實(shí)已經(jīng)并不是多版本了,又回到了單版本的狀態(tài),因?yàn)樗械膶?shí)現(xiàn)都是通過鎖來實(shí)現(xiàn)的。

2、并發(fā)事務(wù)控制

2.1 單版本控制-鎖

先來看鎖,鎖用獨(dú)占的方式來保證在只有一個版本的情況下事務(wù)之間相互隔離,所以鎖可以理解為單版本控制。

在 MySQL 事務(wù)中,鎖的實(shí)現(xiàn)與隔離級別有關(guān)系,在 RR(Repeatable Read)隔離級別下,MySQL 為了解決幻讀的問題,以犧牲并行度為代價(jià),通過 Gap 鎖來防止數(shù)據(jù)的寫入,而這種鎖,因?yàn)槠洳⑿卸炔粔颍瑳_突很多,經(jīng)常會引起死鎖。現(xiàn)在流行的 Row 模式可以避免很多沖突甚至死鎖問題,所以推薦默認(rèn)使用 Row + RC(Read Committed)模式的隔離級別,可以很大程度上提高數(shù)據(jù)庫的讀寫并行度。

2.2 多版本控制-MVCC

多版本控制也叫作 MVCC,是指在數(shù)據(jù)庫中,為了實(shí)現(xiàn)高并發(fā)的數(shù)據(jù)訪問,對數(shù)據(jù)進(jìn)行多版本處理,并通過事務(wù)的可見性來保證事務(wù)能看到自己應(yīng)該看到的數(shù)據(jù)版本。

那個多版本是如何生成的呢?每一次對數(shù)據(jù)庫的修改,都會在 Undo 日志中記錄當(dāng)前修改記錄的事務(wù)號及修改前數(shù)據(jù)狀態(tài)的存儲地址(即 ROLL_PTR),以便在必要的時(shí)候可以回滾到老的數(shù)據(jù)版本。例如,一個讀事務(wù)查詢到當(dāng)前記錄,而最新的事務(wù)還未提交,根據(jù)原子性,讀事務(wù)看不到最新數(shù)據(jù),但可以去回滾段中找到老版本的數(shù)據(jù),這樣就生成了多個版本。

多版本控制很巧妙地將稀缺資源的獨(dú)占互斥轉(zhuǎn)換為并發(fā),大大提高了數(shù)據(jù)庫的吞吐量及讀寫性能。

2.3 MVCC 實(shí)現(xiàn)原理

MySQL InnoDB 存儲引擎,實(shí)現(xiàn)的是基于多版本的并發(fā)控制協(xié)議——MVCC,而不是基于鎖的并發(fā)控制。

MVCC 最大的好處是讀不加鎖,讀寫不沖突。在讀多寫少的 OLTP(On-Line Transaction Processing)應(yīng)用中,讀寫不沖突是非常重要的,極大的提高了系統(tǒng)的并發(fā)性能,這也是為什么現(xiàn)階段幾乎所有的 RDBMS(Relational Database Management System),都支持 MVCC 的原因。

2.4 快照讀與當(dāng)前讀

在 MVCC 并發(fā)控制中,讀操作可以分為兩類: 快照讀(Snapshot Read)與當(dāng)前讀 (Current Read)。

  • 快照讀:讀取的是記錄的可見版本(有可能是歷史版本),不用加鎖。

  • 當(dāng)前讀:讀取的是記錄的最新版本,并且當(dāng)前讀返回的記錄,都會加鎖,保證其他事務(wù)不會再并發(fā)修改這條記錄。

注意:MVCC 只在 Read Commited 和 Repeatable Read 兩種隔離級別下工作。

如何區(qū)分快照讀和當(dāng)前讀呢? 可以簡單的理解為:

  • 快照讀:簡單的 select 操作,屬于快照讀,不需要加鎖。

  • 當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,需要加鎖。

3、并發(fā)事務(wù)問題及解決方案

并發(fā)事務(wù)處理也會帶來一些問題,如:臟讀、不可重復(fù)讀、幻讀。

  • 臟讀:
    一個事務(wù)正在對一條記錄做修改,在這個事務(wù)完成并提交前,這條記錄的數(shù)據(jù)就處于不一致狀態(tài);這時(shí),另一個事務(wù)也來讀取同一條記錄,如果不加控制,第二個事務(wù)讀取了這些“臟”數(shù)據(jù),并據(jù)此做進(jìn)一步的處理,就會產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象的叫作"臟讀"(Dirty Reads)。

  • 不可重復(fù)讀:
    一個事務(wù)在讀取某些數(shù)據(jù)后的某個時(shí)間,再次讀取以前讀過的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!這種現(xiàn)象就叫作“ 不可重復(fù)讀”(Non-Repeatable Reads)。

  • 幻讀:
    一個事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”(Phantom Reads)。

解決方案:
產(chǎn)生的這些問題,MySQL 數(shù)據(jù)庫是通過事務(wù)隔離級別來解決的,如下圖所示:

4、MySQL 鎖分類

在 MySQL 中有三種級別的鎖:頁級鎖、表級鎖、行級鎖。

  • 表級鎖:開銷小,加鎖快;不會出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。 會發(fā)生在:MyISAM、memory、InnoDB、BDB 等存儲引擎中。
    注意:MySQL 中的表鎖包括讀鎖和寫鎖。

  • 行級鎖:開銷大,加鎖慢;會出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度最高。會發(fā)生在:InnoDB 存儲引擎。

  • 頁級鎖:開銷和加鎖時(shí)間界于表鎖和行鎖之間;會出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。會發(fā)生在:BDB 存儲引擎。

三種級別的鎖分別對應(yīng)存儲引擎關(guān)系如下圖所示:

4.1 InnoDB 鎖分類及問題

  • 共享鎖(S),也叫讀鎖:允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。

  • 排他鎖(X),也叫寫鎖:允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和排他寫鎖。

另外,為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制,InnoDB 還有兩種內(nèi)部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。表鎖又分為三種。

  • 意向共享鎖(IS):事務(wù)計(jì)劃給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的 IS 鎖。

  • 意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的 IX 鎖。

  • 自增鎖(AUTO-INC Locks):特殊表鎖,自增長計(jì)數(shù)器通過該“鎖”來獲得子增長計(jì)數(shù)器最大的計(jì)數(shù)值。

在加行鎖之前必須先獲得表級意向鎖,否則等待 innodb_lock_wait_timeout 超時(shí)后根據(jù)innodb_rollback_on_timeout 決定是否回滾事務(wù)。

(1)InnoDB 自增鎖:
在 MySQL InnoDB 存儲引擎中,在設(shè)計(jì)表結(jié)構(gòu)的時(shí)候,通常會建議添加一列作為自增主鍵。這里就會涉及一個特殊的鎖:自增鎖。

(2)InnoDB 行鎖實(shí)現(xiàn)算法:
InnoDB 行鎖是通過對索引數(shù)據(jù)頁上的記錄(record)加鎖實(shí)現(xiàn)的。主要實(shí)現(xiàn)算法有 3 種:Record Lock、Gap Lock 和 Next-key Lock。

(3)排查 InnoDB 鎖問題:

(4)InnoDB 加鎖行為:
下面舉一些例子分析 InnoDB 不同索引的加鎖行為。分析鎖時(shí)需要跟隔離級別聯(lián)系起來,以 RR 為例,主要是從四個場景分析。

  • 主鍵 + RR:
  • 唯一鍵 + RR:
  • 非唯一鍵 + RR:
  • 無索引 + RR:

(5)InnoDB 死鎖:
在 MySQL 中死鎖不會發(fā)生在 MyISAM 存儲引擎中,但會發(fā)生在 InnoDB 存儲引擎中,因?yàn)?InnoDB 是逐行加鎖的,極容易產(chǎn)生死鎖。那么死鎖產(chǎn)生的四個條件是什么呢?

在發(fā)生死鎖時(shí),InnoDB 存儲引擎會自動檢測,并且會自動回滾代價(jià)較小的事務(wù)來解決死鎖問題。但很多時(shí)候一旦發(fā)生死鎖,InnoDB 存儲引擎的處理的效率是很低下的或者有時(shí)候根本解決不了問題,需要人為手動去解決。

既然死鎖問題會導(dǎo)致嚴(yán)重的后果,那么在開發(fā)或者使用數(shù)據(jù)庫的過程中,如何避免死鎖的產(chǎn)生呢?這里給出一些建議:

給大家一些開發(fā)建議來避免線上業(yè)務(wù)因死鎖造成的不必要的影響。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評論 2 377