InnoDB 鎖
數(shù)據(jù)庫使用鎖是為了支持更好的并發(fā)团赏,提供數(shù)據(jù)的完整性和一致性辰晕。InnoDB是一個支持行鎖的存儲引擎梁肿,鎖的類型有:共享鎖(S)、排他鎖(X)觅彰、意向共享(IS)吩蔑、意向排他(IX)。為了提供更好的并發(fā)填抬,
InnoDB提供了非鎖定讀:不需要等待訪問行上的鎖釋放烛芬,讀取行的一個快照。該方法是通過InnoDB的一個特性:MVCC來實現(xiàn)的飒责。
MySQL InnoDB存儲引擎赘娄,實現(xiàn)的是基于多版本的并發(fā)控制協(xié)議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基于鎖的并發(fā)控制读拆,Lock-Based Concurrency Control)擅憔。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖檐晕,讀寫不沖突暑诸。在讀多寫少的OLTP應(yīng)用中,讀寫不沖突是非常重要的辟灰,極大的增加了系統(tǒng)的并發(fā)性能个榕,這也是為什么現(xiàn)階段,幾乎所有的RDBMS芥喇,都支持了MVCC西采。
在MVCC并發(fā)控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當(dāng)前讀 (current read)继控⌒倒荩快照讀,讀取的是記錄的可見版本 (有可能是歷史版本)武通,不用加鎖霹崎。當(dāng)前讀,讀取的是記錄的最新版本冶忱,并且尾菇,當(dāng)前讀返回的記錄,都會加上鎖囚枪,保證其他事務(wù)不會再并發(fā)修改這條記錄派诬。
在一個支持MVCC并發(fā)控制的系統(tǒng)中,哪些讀操作是快照讀链沼?哪些操作又是當(dāng)前讀呢默赂?以MySQL InnoDB為例:
-
快照讀:簡單的select操作,屬于快照讀括勺,不加鎖缆八。(當(dāng)然谒臼,也有例外,下面會分析)
- select * from table where ?;
-
當(dāng)前讀:特殊的讀操作耀里,插入/更新/刪除操作,屬于當(dāng)前讀拾氓,需要加鎖冯挎。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
所有以上的語句,都屬于當(dāng)前讀咙鞍,讀取記錄的最新版本房官。并且,讀取之后续滋,還需要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄翰守,對讀取記錄加鎖。其中疲酌,除了第一條語句蜡峰,對讀取記錄加S鎖 (共享鎖)外,其他的操作朗恳,都加的是X鎖 (排它鎖)湿颅。
共享鎖【S鎖】
又稱讀鎖,若事務(wù)T對數(shù)據(jù)對象A加上S鎖粥诫,則事務(wù)T可以讀A但不能修改A油航,其他事務(wù)只能再對A加S鎖,而不能加X鎖怀浆,直到T釋放A上的S鎖谊囚。這保證了其他事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改执赡。排他鎖【X鎖】
又稱寫鎖镰踏。若事務(wù)T對數(shù)據(jù)對象A加上X鎖,事務(wù)T可以讀A也可以修改A搀玖,其他事務(wù)不能再對A加任何鎖余境,直到T釋放A上的鎖。這保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A灌诅。
為什么將 插入/更新/刪除 操作芳来,都?xì)w為當(dāng)前讀?可以看看下面這個 更新 操作猜拾,在數(shù)據(jù)庫中的執(zhí)行流程:
從圖中即舌,可以看到,一個Update操作的具體流程挎袜。當(dāng)Update SQL被發(fā)給MySQL后顽聂,MySQL Server會根據(jù)where條件肥惭,讀取第一條滿足條件的記錄,然后InnoDB引擎會將第一條記錄返回紊搪,并加鎖 (current read)蜜葱。待MySQL Server收到這條加鎖的記錄之后,會再發(fā)起一個Update請求耀石,更新這條記錄牵囤。一條記錄操作完成,再讀取下一條記錄滞伟,直至沒有滿足條件的記錄為止揭鳞。因此,Update操作內(nèi)部梆奈,就包含了一個當(dāng)前讀野崇。同理,Delete操作也一樣亩钟。Insert操作會稍微有些不同乓梨,簡單來說,就是Insert操作可能會觸發(fā)Unique Key的沖突檢查径荔,也會進(jìn)行一個當(dāng)前讀督禽。
MySQL/InnoDB定義的4種隔離級別:
Read Uncommited
可以讀取未提交記錄。此隔離級別总处,不會使用狈惫,忽略。
Read Committed (RC)
快照讀:忽略鹦马,本文不考慮胧谈。
當(dāng)前讀:RC隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現(xiàn)象荸频。
Repeatable Read (RR)
快照讀:忽略菱肖,本文不考慮。
當(dāng)前讀:RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖)旭从,同時保證對讀取的范圍加鎖稳强,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現(xiàn)象和悦。注意:這里的不存在幻讀退疫,是指使用select ... for update 在同一個事務(wù)中查詢,不會出現(xiàn)兩次不一樣的結(jié)果
Serializable
從MVCC并發(fā)控制退化為基于鎖的并發(fā)控制鸽素。不區(qū)別快照讀與當(dāng)前讀褒繁,所有的讀操作均為當(dāng)前讀,讀加讀鎖 (S鎖)馍忽,寫加寫鎖 (X鎖)棒坏。
Serializable隔離級別下燕差,讀寫沖突,因此并發(fā)度急劇下降坝冕,在MySQL/InnoDB下不建議使用徒探。
上面說的當(dāng)前讀就是上面列出來的 select .. for update, update , delete, insert 等語句
加鎖情況
我們分析一下 RR RC級別下配合不同索引情況的加鎖情況
表goods 定義是 :
id: 主鍵
name: unique key
stock :無索引
組合一:id主鍵+RC
--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods ;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 1 | prod11 | 15 |
+----+--------+-------+
mysql> update goods set stock =20 where id =1
# 這里已經(jīng)鎖住了id=1的記錄, 因為上面說了update喂窟,delete, insert, select ... for update都會讀當(dāng)前讀刹帕,會觸發(fā)鎖機制
# 所以用這幾個任何一個命令都能鎖住記錄
mysql> select * from goods where id = 1 for update;
# 同樣鎖住id=1的記錄
--------------------- SESSION 2 -------------------------
mysql> select * from goods where id=1;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 1 | prod11 | 15 |
+----+--------+-------+
1 row in set (0.00 sec)
# 默認(rèn)的select 不會使用鎖,它是快照讀谎替,不是當(dāng)前讀
mysql> select * from goods where id=1 for update;
# 這里會阻塞直到session 1事務(wù)結(jié)束
結(jié)論:id是主鍵時,此SQL只需要在id=1這條記錄上加X鎖即可蹋辅。
組合二:id唯一索引+RC
--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods where name="prod12" for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 2 | prod12 | 1000 |
+----+--------+-------+
--------------------- SESSION 2 -------------------------
mysql> update goods set stock =20 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update goods set stock =20 where name="prod12";
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
# 使用唯一索引的時候钱贯, 無論使用唯一索引name, 還是主鍵索引id 都不能讀到當(dāng)前讀,這是因為唯一索引會把唯一索引和主鍵索引都加鎖
此組合中侦另,name是unique索引秩命,而主鍵是id列。此時褒傅,加鎖的情況由于組合一有所不同弃锐。由于name是unique索引,因此delete語句會選擇走name列的索引進(jìn)行where條件的過濾殿托,在找到name="prod12"的記錄后霹菊,首先會將unique索引上的name="prod12"索引記錄加上X鎖,同時支竹,會根據(jù)讀取到的id列旋廷,回主鍵索引(聚簇索引),然后將聚簇索引上的id=2 對應(yīng)的主鍵索引項加X鎖礼搁。為什么聚簇索引上的記錄也要加鎖饶碘?試想一下,如果并發(fā)的一個SQL馒吴,是通過主鍵索引來更新:update goods set name= "prod30" where id=2; 此時扎运,如果delete語句沒有將主鍵索引上的記錄加鎖,那么并發(fā)的update就會感知不到delete語句的存在饮戳,違背了同一記錄上的更新/刪除需要串行執(zhí)行的約束豪治。
所以主鍵和唯一索引都要加X鎖,防止別的update, delete 會使用主鍵來查詢修改
結(jié)論 若name列是unique列莹捡,其上有unique索引鬼吵。那么SQL需要加兩個X鎖,一個對應(yīng)于name unique索引上的name="prod12"的記錄篮赢,另一把鎖對應(yīng)于聚簇索引上的[id=2, name="prod"]的記錄齿椅。
組合三:非唯一索引+RC
跟組合二差不多琉挖,與組合二唯一的區(qū)別在于,組合二最多只有一個滿足等值查詢的記錄涣脚,而組合三會將所有滿足查詢條件的記錄都加鎖示辈。結(jié)論 對應(yīng)的所有滿足SQL查詢條件的記錄,都會被加鎖遣蚀。同時矾麻,這些記錄在主鍵索引上的記錄,也會被加鎖芭梯。
--------------------- SESSION 1 -------------------------
mysql> select * from goods where id=7
-> ;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 7 | prod17 | 107 |
+----+--------+-------+
1 row in set (0.00 sec)
mysql> delete from goods where stock=107;
Query OK, 1 rows affected (0.00 sec)
--------------------- SESSION 2 -------------------------
mysql> select * from goods where stock=107 for update;
# 普通索引 被鎖 阻塞
mysql> select * from goods where id=7 for update;
# 聚簇索引(主鍵索引被鎖)
mysql> select * from goods where name=7 for update;
# 其他字段頁被鎖险耀,因為主鍵索引被鎖,整行被鎖
組合四:id無索引+RC
stock去掉索引
--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods where stock=20 for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 5 | prod15 | 20 |
| 23 | prod21 | 20 |
+----+--------+-------+
# 使用stock 來查詢玖喘,stock是沒有索引
--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> select * from goods where id=1 for update
mysql> select * from goods for update;
mysql> update goods set stock =20 where 1;
# 無論查什么當(dāng)前讀甩牺,更新記錄,都被阻塞累奈,說明整個表都被鎖住了贬派。
由于stock列上沒有索引,因此只能走聚簇索引澎媒,進(jìn)行全部掃描搞乏。從圖中可以看到,滿足條件的記錄有兩條戒努,但是请敦,聚簇索引上所有的記錄,都被加上了X鎖储玫。無論記錄是否滿足條件冬三,全部被加上X鎖。既不是加表鎖缘缚,也不是在滿足條件的記錄上加行鎖勾笆。
有人可能會問?為什么不是只在滿足條件的記錄上加鎖呢桥滨?這是由于MySQL的實現(xiàn)決定的窝爪。如果一個條件無法通過索引快速過濾,那么存儲引擎層面就會將所有記錄加鎖后返回齐媒,然后由MySQL Server層進(jìn)行過濾蒲每。因此也就把所有的記錄,都鎖上了喻括。
注:在實際的實現(xiàn)中邀杏,MySQL有一些改進(jìn),在MySQL Server過濾條件,發(fā)現(xiàn)不滿足后望蜡,會調(diào)用unlock_row方法唤崭,把不滿足條件的記錄放鎖 (違背了2PL的約束)。這樣做脖律,保證了最后只會持有滿足條件記錄上的鎖谢肾,但是每條記錄的加鎖操作還是不能省略的。
結(jié)論:若stock列上沒有索引小泉,SQL會走聚簇索引的全掃描進(jìn)行過濾芦疏,由于過濾是由MySQL Server層面進(jìn)行的。因此每條記錄微姊,無論是否滿足條件酸茴,都會被加上X鎖。但是兢交,為了效率考量弊决,MySQL做了優(yōu)化,對于不滿足條件的記錄魁淳,會在判斷后放鎖,最終持有的与倡,是滿足條件的記錄上的鎖界逛,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略。同時纺座,優(yōu)化也違背了2PL的約束息拜。
注意!>幌臁I倨邸! 這里說的對不滿足條件記錄會有 加鎖\放鎖的動作馋贤,但是T薇稹!實際操作中配乓,還是所有記錄都被鎖住了仿滔,根本沒有放鎖?這是為什么犹芹?崎页?
組合五:id主鍵+RR
這個跟 id主鍵+RC組合效果一樣拂募,都是鎖住被查詢出來的記錄
組合六: id唯一索引+RR
這個跟 id唯一索引+RC組合效果一樣异逐,都是將唯一索引和聚簇索引的記錄鎖住
組合七:id非唯一索引+RR
還記得前面提到的MySQL的四種隔離級別的區(qū)別嗎?RC隔離級別允許幻讀悼沿,而RR隔離級別屿笼,不允許存在幻讀牺荠。但是在組合五翁巍、組合六中,加鎖行為又是與RC下的加鎖行為完全一致志电。那么RR隔離級別下曙咽,如何防止幻讀呢?問題的答案挑辆,就在組合七中揭曉例朱。
我們先看看如果是級別RC,出現(xiàn)的幻讀情況
--------------------- SESSION 1 -------------------------
# RC級別
mysql> begin;
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 21 | prod20 | 15 |
| 22 | prod22 | 15 |
+----+--------+-------+
--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> insert into goods values(24, 'prod24', 15);
# 這里依然可以插入stock=15的記錄鱼蝉,因為session 1直接對21 22兩條stock=15記錄加鎖了
mysql> commit;
--------------------- SESSION 1 -------------------------
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 21 | prod20 | 15 |
| 22 | prod22 | 15 |
| 24 | prod24 | 15 |
+----+--------+-------+
# 多了session 2提交的數(shù)據(jù)洒嗤,同一個事務(wù)兩次select for update 居然不同了。 這就是幻讀?唷渔隶!
再看看 RR級別會不會出現(xiàn)幻讀
--------------------- SESSION 1 -------------------------
# RR級別
mysql> begin;
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 21 | prod20 | 15 |
| 22 | prod22 | 15 |
+----+--------+-------+
--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> insert into goods values(24, 'prod24', 15);
# 這里跟RC級別不一樣了, stock=15的24記錄根本插不進(jìn)去洁奈!
# 這就是為什么RR級別不會出現(xiàn)幻讀的原因间唉,因為不能給其他事務(wù)插足
RR與RC 幻讀總結(jié)
RR隔離級別其實這個多出來的GAP鎖,相對于RC隔離級別利术,不會出現(xiàn)幻讀的關(guān)鍵呈野。確實,GAP鎖鎖住的位置印叁,也不是記錄本身被冒,而是兩條記錄之間的GAP。所謂幻讀轮蜕,就是同一個事務(wù)昨悼,連續(xù)做兩次當(dāng)前讀 (例如:select * from goods where stock=15 for update;),那么這兩次當(dāng)前讀返回的是完全相同的記錄 (記錄數(shù)量一致跃洛,記錄本身也一致)率触,第二次的當(dāng)前讀,不會比第一次返回更多的記錄 (幻象)汇竭。
如何保證兩次當(dāng)前讀返回一致的記錄闲延,那就需要在第一次當(dāng)前讀與第二次當(dāng)前讀之間,其他的事務(wù)不會插入新的滿足條件的記錄并提交韩玩。為了實現(xiàn)這個功能垒玲,GAP鎖應(yīng)運而生。
mysql> select * from goods order by stock;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 7 | prod17 | 10 |
| 8 | prod18 | 10 |
| 22 | prod22 | 15 |
| 21 | prod20 | 15 |
| 23 | prod21 | 20 |
| 5 | prod15 | 20 |
| 10 | pro10 | 50
+----+--------+-------+
上面的記錄所示找颓,有哪些位置可以插入新的滿足條件的項 stock= 15合愈,考慮到B+樹索引的有序性,stock索引的存儲一定是有序的,滿足條件的項一定是連續(xù)存放的佛析。
所以stock在[10, 15]之間益老, [15, 20]之間都是可以被其他事務(wù)插入stock=15的記錄的。因此要想杜絕幻讀寸莫,這個gap鎖也就是間隙鎖捺萌,必須鎖住10-15, 15-20之間的記錄。
我們繼續(xù)看session 2, 上面示例看到不能插入stock=15的記錄膘茎,其實10-15, 15-20之間都是不可以插入的
--------------------- SESSION 2 -------------------------
mysql> insert into goods values('', 'prod24', 11);
mysql> insert into goods values('', 'prod24', 13);
mysql> insert into goods values('', 'prod24', 18);
mysql> insert into goods values('', 'prod24', 19);
# 在10-15桃纯, 15-20之間的記錄都不能插入,因為他們都有可能被放入stock=15的記錄
mysql> insert into goods values('', 'prod24', 21);
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> insert into goods values('', 'prod24', 8);
Query OK, 1 row affected, 1 warning (0.00 sec)
# 超出這個范圍的是可以插入的披坏。
如果我們查的stock=14沒有數(shù)據(jù)态坦,那么會不會也有g(shù)ap鎖?答案是:有的棒拂。
我發(fā)現(xiàn)如果索引是主鍵也會有這個gap鎖伞梯,當(dāng)然查詢的是一個范圍
--------------------- SESSION 1 -------------------------
mysql> select * from goods where id > 16 for update;
+----+--------+-------+
| id | name | stock |
+----+--------+-------+
| 21 | prod20 | 15 |
| 22 | prod22 | 15 |
| 23 | prod21 | 20 |
+----+--------+-------+
--------------------- SESSION 2 -------------------------
mysql> insert into goods values(9, 'prod24', 11);
ERROR 1062 (23000): Duplicate entry '9' for key 'PRIMARY'
mysql> insert into goods values(12, 'prod24', '');
# 發(fā)現(xiàn)>16的 還有10-15這個區(qū)間也被鎖住了
總結(jié) 只要是范圍查詢,都會有g(shù)ap鎖帚屉。
組合八:id無索引+RR
如圖谜诫,這是一個很恐怖的現(xiàn)象。首先攻旦,聚簇索引上的所有記錄喻旷,都被加上了X鎖。其次敬特,聚簇索引每條記錄間的間隙(GAP),也同時被加上了GAP鎖牺陶。這個示例表伟阔,只有6條記錄,一共需要6個記錄鎖掰伸,7個GAP鎖皱炉。試想,如果表上有1000萬條記錄呢狮鸭?
在這種情況下合搅,這個表上,除了不加鎖的快照度歧蕉,其他任何加鎖的并發(fā)SQL灾部,均不能執(zhí)行,不能更新惯退,不能刪除赌髓,不能插入,全表被鎖死。
當(dāng)然锁蠕,跟組合四類似夷野,這個情況下,MySQL也做了一些優(yōu)化荣倾,就是所謂的semi-consistent read悯搔。semi-consistent read開啟的情況下,對于不滿足查詢條件的記錄舌仍,MySQL會提前放鎖妒貌。針對上面的這個用例,就是除了記錄[d,10]抡笼,[g,10]之外苏揣,所有的記錄鎖都會被釋放,同時不加GAP鎖推姻。semi-consistent read如何觸發(fā):要么是read committed隔離級別平匈;要么是Repeatable Read隔離級別,同時設(shè)置了 innodb_locks_unsafe_for_binlog 參數(shù)
結(jié)論:在Repeatable Read隔離級別下藏古,如果進(jìn)行全表掃描的當(dāng)前讀增炭,那么會鎖上表中的所有記錄,同時會鎖上聚簇索引內(nèi)的所有GAP拧晕,杜絕所有的并發(fā) 更新/刪除/插入 操作隙姿。當(dāng)然,也可以通過觸發(fā)semi-consistent read厂捞,來緩解加鎖開銷與并發(fā)影響输玷,但是semi-consistent read本身也會帶來其他問題,不建議使用靡馁。
組合九:Serializable
Serializable隔離級別欲鹏,影響的是SQL1:select * from t1 where id = 10; 這條SQL,在RC臭墨,RR隔離級別下赔嚎,都是快照讀,不加鎖胧弛。但是在Serializable隔離級別尤误,SQL1會加讀鎖,也就是說快照讀不復(fù)存在结缚,MVCC并發(fā)控制降級為Lock-Based CC损晤。
結(jié)論:在MySQL/InnoDB中,所謂的讀不加鎖红竭,并不適用于所有的情況沉馆,而是隔離級別相關(guān)的码党。Serializable隔離級別,讀不加鎖就不再成立斥黑,所有的讀操作揖盘,都是當(dāng)前讀。
鎖總結(jié)
- 在MVCC(基于多版本的并發(fā)控制協(xié)議)并發(fā)控制中锌奴,讀操作可以分成兩類:快照讀 (snapshot read)與當(dāng)前讀 (current read)兽狭。
- 2PL (二階段鎖):Two-Phase Locking。說的是鎖操作分為兩個階段:加鎖階段與解鎖階段鹿蜀,并且保證加鎖階段與解鎖階段不相交箕慧。
- 主鍵索引(聚簇索引) id主鍵+RC :只會在匹配的主鍵上加X鎖
- id唯一索引+RC: id是unique索引,而主鍵是name列茴恰。此時颠焦,由于id是unique索引,因此delete語句會選擇走id列的索引進(jìn)行where條件的過濾往枣,在找到id=10的記錄后伐庭,首先會將unique索引上的id=10索引記錄加上X鎖(注意:這里是給索引加鎖,innodb的二級索引只會帶上主鍵索引數(shù)據(jù)分冈,其他數(shù)據(jù)需要回行查詢)圾另,同時,會根據(jù)讀取到的name列雕沉,回主鍵索引(聚簇索引)集乔,然后將聚簇索引上的name = ‘d’ 對應(yīng)的主鍵索引項加X鎖(注意:由于聚簇索引本身就是帶上行數(shù)據(jù),所以要真正鎖這個聚簇索引才能真正鎖行坡椒!)扰路。為什么聚簇索引上的記錄也要加鎖?試想一下倔叼,如果并發(fā)的一個SQL汗唱,是通過主鍵索引來更新:update t1 set id = 100 where name = ‘d’; 此時,如果delete語句沒有將主鍵索引上的記錄加鎖缀雳,那么并發(fā)的update就會感知不到delete語句的存在渡嚣,違背了同一記錄上的更新/刪除需要串行執(zhí)行的約束梢睛。刪除需要串行執(zhí)行的約束**
- id非唯一索引+RC :也是會同時鎖匹配的索引和指向的主鍵索引肥印,跟上面那個原理一樣,只不過這個非唯一索引匹配多條記錄绝葡。(同樣道理深碱,鎖住普通索引后,還得鎖聚簇索引才行)
- id無索引+RC:若id列上沒有索引藏畅,SQL會走聚簇索引的全掃描進(jìn)行過濾敷硅,由于過濾是由MySQL Server層面進(jìn)行的功咒。因此每條記錄,無論是否滿足條件绞蹦,都會被加上X鎖力奋。但是,為了效率考量幽七,MySQL做了優(yōu)化景殷,對于不滿足條件的記錄,會在判斷后放鎖澡屡,最終持有的猿挚,是滿足條件的記錄上的鎖,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略驶鹉。同時绩蜻,優(yōu)化也違背了2PL的約束。
- id主鍵+RR: 同 id主鍵+RC室埋,因為他們都是精確到記錄办绝,所以就加在主鍵索引上就可以了
- id唯一索引+RR 同id唯一索引+RC 兩個X鎖,id唯一索引滿足條件的記錄上一個词顾,對應(yīng)的聚簇索引上的記錄一個八秃。
-
id非唯一索引+RR 幻讀的重要分界點:
- RC級別下,session1查詢的select .. where id=7 for update, (非唯一索引)肉盹,查出有2條記錄昔驱, session2事務(wù)插入一個id=7的記錄(為什么可以插入?:因為session1的 where id=7 for update, 鎖住了普通索引索引7上忍,和主鍵索引比如a 和 b 骤肛, 那么此時插入id=7的數(shù)據(jù)時可以的,因為新插入的數(shù)據(jù)窍蓝,首先新的主鍵腋颠,沒有被鎖, 而且索引7的兩條記錄是被鎖住吓笙,但是新加的記錄沒有被鎖淑玫。所以可以繼續(xù)插入,session2 這時commit事務(wù)面睛,session1 再次查詢select .. where id=7 for update絮蒿, 就會出現(xiàn)3條記錄,這就是RC級別的幻讀叁鉴;)
- RR級別下土涝,session1查詢的select .. where id=7 for update, (非唯一索引),查出有2條記錄幌墓,session2事務(wù)插入一個id=7的記錄但壮,這里就是跟RC級別的最大差別冀泻,因為這時的插入時被阻塞的,不能插進(jìn)去的蜡饵!這就使得幻讀不能出現(xiàn)弹渔,因為根本不允許插入。為什么溯祸?因為gap鎖捞附,gap鎖在索引id(非唯一索引)的前后都加了鎖,不允許這中間再出現(xiàn)可能的數(shù)據(jù)
死鎖原理與分析
--------------------- SESSION 1 -------------------------
mysql> select * from goods where id =1 for update;
--------------------- SESSION 2 -------------------------
mysql> update goods set stock=120 where id=4;
# 兩個session 各自維護(hù)一個鎖
--------------------- SESSION 1 -------------------------
mysql> select * from goods where id =4 for update;
# 阻塞中您没,因為鎖再session2
--------------------- SESSION 2 -------------------------
update goods set stock=120 where id=1;
# 死鎖出現(xiàn)
session1 提示死鎖:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
sesseion2 執(zhí)行成功
Deadlock found when trying to get lock; try restarting transaction
說明innodb會檢測死鎖
死鎖的發(fā)生與否鸟召,并不在于事務(wù)中有多少條SQL語句,死鎖的關(guān)鍵在于:兩個(或以上)的Session加鎖的順序不一致氨鹏。而使用本文上面提到的欧募,分析MySQL每條SQL語句的加鎖規(guī)則,分析出每條語句的加鎖順序仆抵,然后檢查多個并發(fā)SQL間是否存在以相反的順序加鎖的情況跟继,就可以分析出各種潛在的死鎖情況,也可以分析出線上死鎖發(fā)生的原因镣丑。
參考
重點好文:MySQL 加鎖處理分析
Innodb鎖機制:Next-Key Lock 淺談
MySQL 四種事務(wù)隔離級的說明
Innodb中的事務(wù)隔離級別和鎖的關(guān)系
MySQL中的鎖(表鎖舔糖、行鎖)
mysql、innodb和加鎖分析