Mysql的鎖和隔離機(jī)制(InnoDB引擎)

對于DB來說,經(jīng)常會面對并發(fā)問題逻锐,但是開發(fā)的時候DB總是能很好的解決并發(fā)的問題。那么面對并發(fā)DB是怎么進(jìn)行控制的呢?之前一段時間總是對Mysql的鎖機(jī)制概念十分模糊,什么時候加鎖馍刮?加什么鎖?鎖住之后會是怎么樣窃蹋?

需要明確的點(diǎn)####

首先卡啰,鎖是為了解決數(shù)據(jù)庫事務(wù)并發(fā)問題引入的特性静稻,在Mysql中鎖的行為是和mysql隔離機(jī)制有關(guān)的,畢竟鎖是用來解決DB的隔離性和一致性的匈辱。并不是任何操作都是需要加鎖的振湾,讀操作是不加鎖的,當(dāng)然也可以顯式的加鎖(lock in share mode或for update)亡脸。

Mysql鎖的類型####

Mysql因?yàn)橛泻芏喾N存儲引擎押搪,導(dǎo)致它的實(shí)現(xiàn)也是五花八門,但是最常用的就應(yīng)該是MyISAM和InnoDB了浅碾。對于兩者的區(qū)別之前也寫過大州,其中有一點(diǎn)是MyISAM鎖級別是表級而InnoDB的鎖級別是行級(當(dāng)然InnoDB也有表級鎖)。mysql鎖的類別如下:
表級鎖:開銷小垂谢,加鎖快摧茴;不會出現(xiàn)死鎖;鎖定粒度大埂陆,發(fā)生鎖沖突的概率最高,并發(fā)度最低。
行級鎖:開銷大娃豹,加鎖慢焚虱;會出現(xiàn)死鎖;鎖定粒度最小懂版,發(fā)生鎖沖突的概率最低,并發(fā)度也最高鹃栽。
頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現(xiàn)死鎖躯畴;鎖定粒度界于表鎖和行鎖之間民鼓,并發(fā)度一般。
不同的鎖粒度決定了不同引擎的應(yīng)用場景蓬抄,我們最常用的表級鎖的引擎是MyISAM和InnoDB丰嘉,行級引擎是InnoDB。至于頁級鎖的引擎常用的是Berkeley DB嚷缭。

Mysql的鎖####

Mysql的鎖主要為兩種:共享鎖(S Lock)和排他鎖(X Lock)饮亏。從字面上我們可以理解,共享鎖就是多個事務(wù)可以共享阅爽,互相兼容路幸。而排他鎖則是多個事務(wù)不兼容互相排斥。
如果一個事務(wù)T1獲得了r行的共享鎖付翁,那么另外一個事務(wù)T2可以立即獲得r的共享鎖简肴,這種情況稱為“鎖兼容”。如果有T3想獲得r行的排他鎖必須等到T1百侧、T2釋放r行的共享鎖砰识,這種稱為“鎖不兼容”能扒,下表對應(yīng)的是鎖兼容性:

Paste_Image.png

可以看到只有共享鎖是兼容的,也就是說讀請求和讀請求之間是沒有影響的仍翰。
InnoDB為了支持在不同粒度上加鎖操作赫粥,InnoDB支持另一種加鎖機(jī)制——意向鎖。意向鎖的意思很簡單予借,就是有意愿進(jìn)行加鎖越平。
意向共享鎖(IS Lock):事務(wù)想要獲取一張表中的某幾行共享鎖。
意向排他鎖(IX Lock):事務(wù)想要獲取一張表中的某幾行的排它鎖灵迫。
由于InnoDB支持的行級別的鎖秦叛,因此意向鎖其實(shí)不會阻塞除全表掃描意外的任何請求。意向鎖的兼容性如下所示:

Paste_Image.png

意向鎖和意向鎖之間是完全兼容的瀑粥,但是意向鎖和共享鎖以及排它鎖可能是有互斥性的挣跋。因?yàn)橐庀蜴i的鎖粒度是表級鎖,所以在全表掃描是往往會對表加鎖狞换,那么此時就會發(fā)生鎖沖突避咆。
之前一直不明白意向鎖到底是干什么的,相信很多人和我一樣修噪,后來查了很多資料才知道查库,有一個很形象的例子:
如果你家小區(qū)有一個保安,那么就能避免經(jīng)常有人去按你家的門鎖...
保安就是意向鎖黄琼,它能避免經(jīng)常有請求去請求行級鎖樊销,因?yàn)樵L問行級鎖也是有一定開銷的。

上面說的東西概念性都比較強(qiáng)脏款,但是千萬別被誤導(dǎo)围苫,因?yàn)樯厦娴母拍钤趯?shí)際的查詢中不一定全都會使用,例如mysql的讀操作撤师,通常是不會加鎖的(和隔離機(jī)制有關(guān))剂府,也就是說通常的讀操作是不加鎖的,而是通過mvcc去解決的丈氓,對于通常的寫請求周循,insert、update万俗、delete通常會加行鎖湾笛、間隙鎖或表鎖(這和索引是有關(guān)系的),這些鎖通常是排他的闰歪,會阻塞其他的事務(wù)寫事務(wù)嚎研。具體的情況需要結(jié)合隔離機(jī)制。

Mysql的隔離性####

隔離性是指一個事務(wù)所做的修改在最終提交之前,對其他的事務(wù)是不可見的临扮。
mysql的隔離性分為四個隔離級別论矾,不同的隔離級別有不同的特點(diǎn)和實(shí)現(xiàn):
1.Read Uncommitted(臟讀):從隔離級別的名稱可知,事務(wù)可以讀取到其他沒有commit的事務(wù)的修改杆勇,所以稱為臟讀贪壳,因?yàn)樽x取到了本來不應(yīng)該讀到的記錄,此事務(wù)隔離級別一般是不會用的蚜退,因?yàn)槿绻竺媪硪粋€事務(wù)rollback掉了闰靴,豈不是悲劇了?
2.Read Committed(提交讀钻注,也叫不可重復(fù)讀):只能讀取到已經(jīng)提交的數(shù)據(jù)蚂且。Oracle等多數(shù)數(shù)據(jù)庫默認(rèn)都是該級別 (不重復(fù)讀)。對于此級別的隔離幅恋,比較上面的臟讀是會嚴(yán)格一些的杏死,例如事務(wù)1開始查詢了一條記錄,但是隨后另一個事務(wù)2修改了本條記錄捆交,此時事務(wù)1再次進(jìn)行讀取淑翼,此時是讀取不到的因?yàn)槭聞?wù)2沒有進(jìn)行commit,隨后事務(wù)2commit品追,事務(wù)1再次讀取窒舟,可以讀到最新修改后的記錄。這比臟讀更加嚴(yán)格了一些诵盼,因?yàn)樽x取不到未提交的數(shù)據(jù),但是此種隔離級別在同一個事務(wù)(事務(wù)1中)兩次讀取银还,讀取到了不同的結(jié)果风宁,這也就是不可重復(fù)讀。
在RC級別中蛹疯,數(shù)據(jù)的讀取都是不加鎖的戒财,但是數(shù)據(jù)的寫入、修改和刪除是需要加鎖的捺弦。
一個例子:

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `stu_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_student_id` (`stu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|  1 | 語文 |      1 |
|  2 | 數(shù)學(xué) |      2 |
|  3 | 英語 |      1 |
+----+------+--------+
3 rows in set

上面是student表內(nèi)的數(shù)據(jù)饮寞,接下來設(shè)置事務(wù)隔離級別為RC
SET session transaction isolation level read committed;
SET SESSION binlog_format = 'ROW';
接下來測試一下update的行鎖:

T1 T2
update student set name = '生物' where stu_id = 2;
update student set name = '生物' where stu_id = 2;
更新成功 阻塞
commit
更新成功

上面的update例子說明,在更新記錄的時候會對此記錄加行鎖列吼,在事務(wù)沒有commit之前不會釋放鎖幽崩,所以事務(wù)2的更新會阻塞等待事務(wù)1的排它鎖,當(dāng)事務(wù)1Commit后寞钥,行鎖釋放事務(wù)2獲得行鎖慌申,更新成功。
其實(shí)mysql的鎖機(jī)制是通過對索引加鎖理郑,但是一旦更新不走索引會怎么樣蹄溉,答案是會全表掃描咨油,鎖表。所以在更新的時候盡量走索引柒爵,避免不必要的麻煩役电,具體這種索引和鎖的問題推薦一篇博客:http://hedengcheng.com/?p=771#_Toc374698322
接下來實(shí)驗(yàn)一下RC基本寫的不可重復(fù)讀:
事務(wù)1:

mysql> begin;
Query OK, 0 rows affected

mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|  2 | 生物 |      2 |
+----+------+--------+
1 row in set

事務(wù)2:

mysql> begin;
Query OK, 0 rows affected

mysql> update student set name = '地理' where stu_id = 2;
Query OK, 1 row affected
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected

接下來事務(wù)1再次查詢:

mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|  2 | 地理 |      2 |
+----+------+--------+
1 row in set

上述過程可見,帶事務(wù)1的一個事務(wù)中棉胀,兩次請求得到了不同的結(jié)果法瑟,就導(dǎo)致了不可重復(fù)讀的現(xiàn)象。

3.Repeatable Read(可重讀或者叫幻讀):RR解決了臟讀的問題膏蚓,該級別保證了在同一個事務(wù)中多次讀取同樣記錄的結(jié)果是一致的瓢谢。
例子和上面RC中的例子一樣,只不過在事務(wù)2提交時驮瞧,事務(wù)1再次查詢是看不到事務(wù)1更新的記錄的氓扛,所以叫可重復(fù)讀,但是理論上這種方式只能解決更新問題论笔,但是解決不了新增的問題采郎,因?yàn)闊o論RC還是RR,mysql都是通過Mvcc(Multi-Version Concurrency Control )機(jī)制去實(shí)現(xiàn)的狂魔。
Mvcc是多版本的并發(fā)控制協(xié)議酌摇,它和基于鎖的并發(fā)控制最大的區(qū)別和優(yōu)點(diǎn)是:讀不加鎖,讀寫不沖突暑刃。它將每一個更新的數(shù)據(jù)標(biāo)記一個版本號铲敛,在更新時進(jìn)行版本號的遞增,插入時新建一個版本號籽孙,同時舊版本數(shù)據(jù)存儲在undo日志中烈评。
而對于讀操作,因?yàn)槎喟姹镜囊敕附ǎ头譃榭煺兆x和當(dāng)前讀讲冠。快照讀只是針對于目標(biāo)數(shù)據(jù)的版本小于等于當(dāng)前事務(wù)的版本號适瓦,也就是說讀數(shù)據(jù)的時候可能讀到舊的數(shù)據(jù)竿开,但是這種快照讀不需要加鎖,性能很高玻熙。當(dāng)前讀是讀取當(dāng)前數(shù)據(jù)的最新版本否彩,但是更新等操作會對數(shù)據(jù)進(jìn)行加鎖,所以當(dāng)前讀需要獲取記錄的行鎖嗦随,存在鎖爭用的問題胳搞。
RC和RR都是基于Mvcc實(shí)現(xiàn),但是讀取的快照數(shù)據(jù)是不同的。RC級別下肌毅,對于快照讀筷转,讀取的總是最新的數(shù)據(jù),也就出現(xiàn)了上面的例子悬而,一個事務(wù)中兩次讀到了不同的結(jié)果呜舒。而RR級別總是讀到小于等于此事務(wù)的數(shù)據(jù),也就實(shí)現(xiàn)了可重讀笨奠。
下面是快照讀和當(dāng)前讀的常見操作:

  1. 快照讀:就是select
    select * from table ....;
  2. 當(dāng)前讀:特殊的讀操作(加共享鎖或排他鎖)袭蝗,插入/更新/刪除操作,需要加鎖般婆。
    select * from table where ? lock in share mode;
    select * from table where ? for update;
    insert;
    update ;
    delete;

其實(shí)Mysql實(shí)現(xiàn)的Mvcc并不純粹到腥,因?yàn)樵诋?dāng)前讀的時候需要對記錄進(jìn)行加鎖,而不是多版本競爭蔚袍。下面是具體操作時的Mvcc機(jī)制:

  1. SELECT時乡范,讀取創(chuàng)建版本號<=當(dāng)前事務(wù)版本號,刪除版本號為空或>當(dāng)前事務(wù)版本號啤咽。
  2. INSERT時晋辆,保存當(dāng)前事務(wù)版本號為行的創(chuàng)建版本號
  3. DELETE時,保存當(dāng)前事務(wù)版本號為行的刪除版本號
  4. UPDATE時宇整,插入一條新紀(jì)錄瓶佳,保存當(dāng)前事務(wù)版本號為行創(chuàng)建版本號,同時保存當(dāng)前事務(wù)版本號到原來刪除的行

上面說明了RR是如何解決重讀問題鳞青,但是眾所周知霸饲,RR有一個致命的問題就是幻讀,即只能解決另一個事務(wù)2更新對事務(wù)1不可見的問題臂拓,但是當(dāng)事務(wù)2新插入一行數(shù)據(jù)的時候贴彼,事務(wù)1還是可見,這就是幻讀問題埃儿。但是在實(shí)際使用中,我們發(fā)現(xiàn)并沒有發(fā)生“幻讀”問題融涣。那么童番,Mysql是如何解決幻讀問題的呢?

我們分兩個方面說:

1.快照讀:對于快照讀威鹿,其實(shí)是不會出現(xiàn)幻讀問題的剃斧,通過上面我們得知,select時只會讀取小于等于當(dāng)前事務(wù)版本的行忽你,但是新行的版本號是高于讀事務(wù)的幼东,那么新插入的行對之前的讀事務(wù)是不可見的。

2.當(dāng)前讀:因?yàn)楫?dāng)前讀,讀到的往往是最新的行數(shù)據(jù)根蟹,但是對于事務(wù)1更新了一行脓杉,同時事務(wù)2插入了一個新行(利用一個非唯一索引進(jìn)行更新),那么會利用gap鎖去控制新行的插入來避免這個問題简逮。一個例子看一下:

首先開啟事務(wù)A:

mysql> begin;
Query OK, 0 rows affected

mysql> select * from student where stu_id =3;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|  2 | 化學(xué) |      3 |
+----+------+--------+
1 row in set
mysql> update student set name = "物理" where stu_id = 3;
Query OK, 1 row affected
Rows matched: 1  Changed: 1  Warnings: 0

接下來開啟事務(wù)B:

mysql> begin;
Query OK, 0 rows affected

mysql> insert into student(id,name,stu_id) values (5,"歷史",3);
Query OK, 1 row affected

我們可以看到球散,事務(wù)A在更新之后,事務(wù)B進(jìn)行插入操作的時候會阻塞散庶,但是這里使用的不是行鎖蕉堰,這就是因?yàn)閞r隔離模式下,mysql使用的是next-keylocking機(jī)制防止“當(dāng)前讀”的幻讀問題悲龟。如果不阻塞新插入的數(shù)據(jù)屋讶,那么就會導(dǎo)致更新之后,再次查詢時會發(fā)現(xiàn)部分?jǐn)?shù)據(jù)沒有更新须教,本意是按照索引更新所有的行皿渗,但是新插入的行沒有更新,這就會令我們很奇怪没卸。

那需要先說說Mysql里面特殊的鎖——Next-Key鎖:
Next-Key鎖是行鎖和Gap鎖(間隙鎖)的合體(可以理解為二者相加羹奉,因?yàn)間ap鎖是開區(qū)間的,加上行鎖正好是閉區(qū)間)约计。間隙鎖诀拭,顧名思義,是對一個間隙進(jìn)行加鎖煤蚌,間隙是索引的間隙耕挨,也就是說,更新的時候必須走索引尉桩,否則會將全表鎖住筒占。導(dǎo)致其他所有的寫操作全部阻塞。next-key鎖主要是針對非唯一索引蜘犁,因?yàn)槲ㄒ凰饕椭麈I索引每次只會定位到單條記錄翰苫,所以不需要next-key鎖,下面盜一張圖來理解下:

Paste_Image.png

當(dāng)按照id(非唯一索引这橙,不是主鍵奏窑,主鍵是name)進(jìn)行更新或刪除的時候會先對id索引進(jìn)行加鎖,但加的是next_key鎖屈扎。因?yàn)樵赗R隔離級別下埃唯,需要防止“當(dāng)前讀”的幻讀問題,加上next-keylock之后鹰晨,在[6-10]區(qū)間和[10-11]區(qū)間進(jìn)行插入時會阻塞墨叛,因?yàn)橐呀?jīng)加了next-key鎖止毕,為什么用next-key鎖?因?yàn)樾略黾拥挠涗浿荒茉?0的左邊和10的右邊或者就是10漠趁。那么鎖住范圍后就能保證防止“幻讀”扁凛。

4.Serializable(可串行化):這個隔離級別,在并發(fā)效果上最差的棚潦,因?yàn)樽x加共享鎖令漂,寫加排他鎖,讀寫互斥丸边。也就是說此級別下select是需要加鎖的叠必。此模式下可以保證數(shù)據(jù)安全,適用于并發(fā)比較低妹窖,同時數(shù)據(jù)安全性要求比較高的場景纬朝。

總結(jié):mysql的鎖機(jī)制和事務(wù)隔離級別有關(guān)。并不是說所有的讀操作都不加鎖骄呼,寫操作加鎖共苛,加什么鎖也和索引類型、有無索引有關(guān)蜓萄。

國慶糾結(jié)了幾天隅茎,總結(jié)了一下,如果有什么錯誤還請指出嫉沽。還有明天得上班了=_=,I am angry~

參考:
https://book.douban.com/subject/23008813/
https://book.douban.com/subject/5373022/
http://tech.meituan.com/innodb-lock.html
http://hedengcheng.com/?p=771

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辟犀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绸硕,更是在濱河造成了極大的恐慌堂竟,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玻佩,死亡現(xiàn)場離奇詭異出嘹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咬崔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門税稼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垮斯,你說我怎么就攤上這事郎仆。” “怎么了甚脉?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铆农。 經(jīng)常有香客問我牺氨,道長狡耻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任猴凹,我火速辦了婚禮夷狰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郊霎。我一直安慰自己沼头,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布书劝。 她就那樣靜靜地躺著进倍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪购对。 梳的紋絲不亂的頭發(fā)上猾昆,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音骡苞,去河邊找鬼垂蜗。 笑死,一個胖子當(dāng)著我的面吹牛解幽,可吹牛的內(nèi)容都是我干的贴见。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼躲株,長吁一口氣:“原來是場噩夢啊……” “哼片部!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起徘溢,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤吞琐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后然爆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體站粟,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年曾雕,在試婚紗的時候發(fā)現(xiàn)自己被綠了奴烙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡剖张,死狀恐怖切诀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搔弄,我是刑警寧澤幅虑,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站顾犹,受9級特大地震影響倒庵,放射性物質(zhì)發(fā)生泄漏褒墨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一擎宝、第九天 我趴在偏房一處隱蔽的房頂上張望郁妈。 院中可真熱鬧,春花似錦绍申、人聲如沸噩咪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胃碾。三九已至,卻和暖如春涂屁,著一層夾襖步出監(jiān)牢的瞬間书在,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工拆又, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儒旬,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓帖族,卻偏偏與公主長得像栈源,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子竖般,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容