原文:http://blog.csdn.net/soonfly/article/details/70238902
鎖是計算機協(xié)調(diào)多個進程或線程并發(fā)訪問某一資源的機制笆檀。在數(shù)據(jù)庫中靡馁,除傳統(tǒng)的 計算資源(如CPU、RAM、I/O等)的爭用以外售碳,數(shù)據(jù)也是一種供許多用戶共享的資源茬故。如何保證數(shù)據(jù)并發(fā)訪問的一致性、有效性是所有數(shù)據(jù)庫必須解決的一 個問題盟榴,鎖沖突也是影響數(shù)據(jù)庫并發(fā)訪問性能的一個重要因素。從這個角度來說婴噩,鎖對數(shù)據(jù)庫而言顯得尤其重要擎场,也更加復雜。本章我們著重討論MySQL鎖機制 的特點几莽,常見的鎖問題迅办,以及解決MySQL鎖問題的一些方法或建議。
Mysql用到了很多這種鎖機制章蚣,比如行鎖站欺,表鎖等,讀鎖纤垂,寫鎖等矾策,都是在做操作之前先上鎖。這些鎖統(tǒng)稱為悲觀鎖(Pessimistic Lock)峭沦。
MySQL鎖概述
相對其他數(shù)據(jù)庫而言贾虽,MySQL的鎖機制比較簡單,其最 顯著的特點是不同的存儲引擎支持不同的鎖機制吼鱼。比如蓬豁,MyISAM和MEMORY存儲引擎采用的是表級鎖(table-level locking);BDB存儲引擎采用的是頁面鎖(page-level locking)菇肃,但也支持表級鎖地粪;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖琐谤,但默認情況下是采用行級鎖蟆技。
表級鎖:開銷小,加鎖快;不會出現(xiàn)死鎖付魔;鎖定粒度大聊品,發(fā)生鎖沖突的概率最高,并發(fā)度最低几苍。
行級鎖:開銷大翻屈,加鎖慢;會出現(xiàn)死鎖妻坝;鎖定粒度最小伸眶,發(fā)生鎖沖突的概率最低,并發(fā)度也最高刽宪。
頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間厘贼;會出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間圣拄,并發(fā)度一般
從上述特點可見嘴秸,很難籠統(tǒng)地說哪種鎖更好,只能就具體應(yīng)用的特點來說哪種鎖更合適庇谆!僅從鎖的角度 來說:表級鎖更適合于以查詢?yōu)橹髟榔挥猩倭堪此饕龡l件更新數(shù)據(jù)的應(yīng)用,如Web應(yīng)用饭耳;而行級鎖則更適合于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù)串述,同時又有 并發(fā)查詢的應(yīng)用,如一些在線事務(wù)處理(OLTP)系統(tǒng)寞肖。
MyISAM表鎖
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨占寫鎖(Table Write Lock)纲酗。
對MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求新蟆,但會阻塞對同一表的寫請求觅赊;對 MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作琼稻;MyISAM表的讀操作與寫操作之間吮螺,以及寫操作之間是串行的!根據(jù)如表20-2所示的 例子可以知道欣簇,當一個線程獲得對一個表的寫鎖后,只有持有鎖的線程可以對表進行更新操作坯约。其他線程的讀熊咽、寫操作都會等待,直到鎖被釋放為止闹丐。
MyISAM存儲引擎的寫鎖阻塞讀例子:
當一個線程獲得對一個表的寫鎖后横殴,只有持有鎖的線程可以對表進行更新操作。其他線程的讀、寫操作都會等待衫仑,直到鎖被釋放為止梨与。
[圖片上傳失敗...(image-ae7939-1571818201159)]
MyISAM存儲引擎的讀鎖阻塞寫例子:
一個session使用LOCK TABLE命令給表film_text加了讀鎖,這個session可以查詢鎖定表中的記錄文狱,但更新或訪問其他表都會提示錯誤粥鞋;同時,另外一個session可以查詢表中的記錄瞄崇,但更新就會出現(xiàn)鎖等待呻粹。
[圖片上傳失敗...(image-c90f4c-1571818201159)]
如何加表鎖
MyISAM在執(zhí)行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖苏研,在執(zhí)行更新操作 (UPDATE等浊、DELETE、INSERT等)前摹蘑,會自動給涉及的表加寫鎖筹燕,這個過程并不需要用戶干預,因此衅鹿,用戶一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖撒踪。在示例中,顯式加鎖基本上都是為了演示而已塘安,并非必須如此糠涛。
給MyISAM表顯示加鎖,一般是為了在一定程度模擬事務(wù)操作兼犯,實現(xiàn)對某一時間點多個表的一致性讀取忍捡。例如, 有一個訂單表orders切黔,其中記錄有各訂單的總金額total砸脊,同時還有一個訂單明細表order_detail,其中記錄有各訂單每一產(chǎn)品的金額小計 subtotal纬霞,假設(shè)我們需要檢查這兩個表的金額合計是否相符凌埂,可能就需要執(zhí)行如下兩條SQL:
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
這時,如果不先給兩個表加鎖诗芜,就可能產(chǎn)生錯誤的結(jié)果瞳抓,因為第一條語句執(zhí)行過程中,order_detail表可能已經(jīng)發(fā)生了改變伏恐。因此孩哑,正確的方法應(yīng)該是:
Lock tables orders read local, order_detail read local;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;
要特別說明以下兩點內(nèi)容:
1、上面的例子在LOCK TABLES時加了“l(fā)ocal”選項翠桦,其作用就是在滿足MyISAM表并發(fā)插入條件的情況下横蜒,允許其他用戶在表尾并發(fā)插入記錄,有關(guān)MyISAM表的并發(fā)插入問題,在后面還會進一步介紹丛晌。
2仅炊、在用LOCK TABLES給表顯式加表鎖時,必須同時取得所有涉及到表的鎖澎蛛,并且MySQL不支持鎖升級抚垄。也就是說,在執(zhí)行LOCK TABLES后瓶竭,只能訪問顯式加鎖的這些表督勺,不能訪問未加鎖的表;同時斤贰,如果加的是讀鎖智哀,那么只能執(zhí)行查詢操作,而不能執(zhí)行更新操作荧恍。其實瓷叫,在自動加鎖的 情況下也基本如此,MyISAM總是一次獲得SQL語句所需要的全部鎖送巡。這也正是MyISAM表不會出現(xiàn)死鎖(Deadlock Free)的原因摹菠。
當使用LOCK TABLES時,不僅需要一次鎖定用到的所有表骗爆,而且次氨,同一個表在SQL語句中出現(xiàn)多少次,就要通過與SQL語句中相同的別名鎖定多少次摘投,否則也會出錯煮寡!舉例說明如下。
(1)對actor表獲得讀鎖:
mysql> lock table actor read;
Query OK, 0 rows affected (0.00 sec)
(2)但是通過別名訪問會提示錯誤:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name
from actor a,actor b
where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom'
and a.last_name <> b.last_name;
ERROR 1100 (HY000): Table ‘a(chǎn)’ was not locked with LOCK TABLES
(3)需要對別名分別鎖定:
mysql> lock table actor as a read,actor as b read;
Query OK, 0 rows affected (0.00 sec)
(4)按照別名的查詢可以正確執(zhí)行:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name
from actor a,actor b where a.first_name = b.first_name
and a.first_name = 'Lisa' and a.last_name = 'Tom'
and a.last_name <> b.last_name;
+————+———–+————+———–+
| first_name | last_name | first_name | last_name |
+————+———–+————+———–+
| Lisa | Tom | LISA | MONROE |
+————+———–+————+———–+
1 row in set (0.00 sec)
查詢表級鎖爭用情況
可以通過檢查table_locks_waited和table_locks_immediate狀態(tài)變量來分析系統(tǒng)上的表鎖定爭奪:
mysql> show status like 'table%';
Variable_name | Value
Table_locks_immediate | 2979
Table_locks_waited | 0
2 rows in set (0.00 sec))
如果Table_locks_waited的值比較高犀呼,則說明存在著較嚴重的表級鎖爭用情況幸撕。
并發(fā)插入(Concurrent Inserts)
上文提到過MyISAM表的讀和寫是串行的,但這是就總體而言的外臂。在一定條件下坐儿,MyISAM表也支持查詢和插入操作的并發(fā)進行。
MyISAM存儲引擎有一個系統(tǒng)變量concurrent_insert宋光,專門用以控制其并發(fā)插入的行為貌矿,其值分別可以為0、1或2罪佳。
- 當concurrent_insert設(shè)置為0時逛漫,不允許并發(fā)插入。
- 當concurrent_insert設(shè)置為1時菇民,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行)尽楔,MyISAM允許在一個進程讀表的同時,另一個進程從表尾插入記錄第练。這也是MySQL的默認設(shè)置阔馋。
- 當concurrent_insert設(shè)置為2時,無論MyISAM表中有沒有空洞娇掏,都允許在表尾并發(fā)插入記錄呕寝。
在下面的例子中,session_1獲得了一個表的READ LOCAL鎖婴梧,該線程可以對表進行查詢操作下梢,但不能對表進行更新操作;其他的線程(session_2)塞蹭,雖然不能對表進行刪除和更新操作孽江,但卻可以對該表進行并發(fā)插入操作,這里假設(shè)該表中間不存在空洞番电。
MyISAM存儲引擎的讀寫(INSERT)并發(fā)例子:
[圖片上傳失敗...(image-cca33b-1571818201158)]
可以利用MyISAM存儲引擎的并發(fā)插入特性岗屏,來解決應(yīng) 用中對同一表查詢和插入的鎖爭用。例如漱办,將concurrent_insert系統(tǒng)變量設(shè)為2这刷,總是允許并發(fā)插入;同時娩井,通過定期在系統(tǒng)空閑時段執(zhí)行 OPTIMIZE TABLE語句來整理空間碎片暇屋,收回因刪除記錄而產(chǎn)生的中間空洞。
MyISAM的鎖調(diào)度
前面講過洞辣,MyISAM存儲引擎的讀鎖和寫鎖是互斥的咐刨,讀寫操作是串行的。那么屋彪,一個進程請求某個 MyISAM表的讀鎖所宰,同時另一個進程也請求同一表的寫鎖,MySQL如何處理呢畜挥?答案是寫進程先獲得鎖仔粥。不僅如此,即使讀請求先到鎖等待隊列蟹但,寫請求后 到躯泰,寫鎖也會插到讀鎖請求之前!這是因為MySQL認為寫請求一般比讀請求要重要华糖。這也正是MyISAM表不太適合于有大量更新操作和查詢操作應(yīng)用的原 因麦向,因為,大量的更新操作會造成查詢操作很難獲得讀鎖客叉,從而可能永遠阻塞诵竭。這種情況有時可能會變得非常糟糕话告!幸好我們可以通過一些設(shè)置來調(diào)節(jié)MyISAM 的調(diào)度行為。
- 通過指定啟動參數(shù)low-priority-updates卵慰,使MyISAM引擎默認給予讀請求以優(yōu)先的權(quán)利沙郭。
- 通過執(zhí)行命令
SET LOW_PRIORITY_UPDATES=1
,使該連接發(fā)出的更新請求優(yōu)先級降低裳朋。 - 通過指定INSERT病线、UPDATE、DELETE語句的LOW_PRIORITY屬性鲤嫡,降低該語句的優(yōu)先級送挑。
雖然上面3種方法都是要么更新優(yōu)先,要么查詢優(yōu)先的方法暖眼,但還是可以用其來解決查詢相對重要的應(yīng)用(如用戶登錄系統(tǒng))中惕耕,讀鎖等待嚴重的問題。
另外诫肠,MySQL也提供了一種折中的辦法來調(diào)節(jié)讀寫沖突赡突,即給系統(tǒng)參數(shù)max_write_lock_count
設(shè)置一個合適的值,當一個表的讀鎖達到這個值后区赵,MySQL就暫時將寫請求的優(yōu)先級降低惭缰,給讀進程一定獲得鎖的機會。
上面已經(jīng)討論了寫優(yōu)先調(diào)度機制帶來的問題和解決辦法笼才。這 里還要強調(diào)一點:一些需要長時間運行的查詢操作漱受,也會使寫進程“餓死”!因此骡送,應(yīng)用中應(yīng)盡量避免出現(xiàn)長時間運行的查詢操作昂羡,不要總想用一條SELECT語 句來解決問題,因為這種看似巧妙的SQL語句摔踱,往往比較復雜虐先,執(zhí)行時間較長,在可能的情況下可以通過使用中間表等措施對SQL語句做一定的“分解”派敷,使每 一步查詢都能在較短時間完成蛹批,從而減少鎖沖突。如果復雜查詢不可避免篮愉,應(yīng)盡量安排在數(shù)據(jù)庫空閑時段執(zhí)行腐芍,比如一些定期統(tǒng)計可以安排在夜間執(zhí)行。
InnoDB鎖
InnoDB與MyISAM的最大不同有兩點:一是支持事務(wù)(TRANSACTION)试躏;二是采用了行級鎖猪勇。行級鎖與表級鎖本來就有許多不同之處,另外颠蕴,事務(wù)的引入也帶來了一些新問題泣刹。
1助析、事務(wù)(Transaction)及其ACID屬性
事務(wù)是由一組SQL語句組成的邏輯處理單元,事務(wù)具有4屬性椅您,通常稱為事務(wù)的ACID屬性貌笨。
- 原子性(Actomicity):事務(wù)是一個原子操作單元,其對數(shù)據(jù)的修改襟沮,要么全都執(zhí)行,要么全都不執(zhí)行昌腰。
- 一致性(Consistent):在事務(wù)開始和完成時开伏,數(shù)據(jù)都必須保持一致狀態(tài)。這意味著所有相關(guān)的數(shù)據(jù)規(guī)則都必須應(yīng)用于事務(wù)的修改遭商,以操持完整性固灵;事務(wù)結(jié)束時,所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如B樹索引或雙向鏈表)也都必須是正確的劫流。
- 隔離性(Isolation):數(shù)據(jù)庫系統(tǒng)提供一定的隔離機制巫玻,保證事務(wù)在不受外部并發(fā)操作影響的“獨立”環(huán)境執(zhí)行。這意味著事務(wù)處理過程中的中間狀態(tài)對外部是不可見的祠汇,反之亦然仍秤。
- 持久性(Durable):事務(wù)完成之后,它對于數(shù)據(jù)的修改是永久性的可很,即使出現(xiàn)系統(tǒng)故障也能夠保持诗力。
2、并發(fā)事務(wù)帶來的問題
相對于串行處理來說我抠,并發(fā)事務(wù)處理能大大增加數(shù)據(jù)庫資源的利用率苇本,提高數(shù)據(jù)庫系統(tǒng)的事務(wù)吞吐量,從而可以支持可以支持更多的用戶菜拓。但并發(fā)事務(wù)處理也會帶來一些問題瓣窄,主要包括以下幾種情況。
- 更新丟失(Lost Update):當兩個或多個事務(wù)選擇同一行纳鼎,然后基于最初選定的值更新該行時俺夕,由于每個事務(wù)都不知道其他事務(wù)的存在,就會發(fā)生丟失更新問題——最后的更新覆蓋了其他事務(wù)所做的更新贱鄙。例如啥么,兩個編輯人員制作了同一文檔的電子副本。每個編輯人員獨立地更改其副本贰逾,然后保存更改后的副本悬荣,這樣就覆蓋了原始文檔。最后保存其更改保存其更改副本的編輯人員覆蓋另一個編輯人員所做的修改疙剑。如果在一個編輯人員完成并提交事務(wù)之前氯迂,另一個編輯人員不能訪問同一文件践叠,則可避免此問題。
- 臟讀(Dirty Reads):一個事務(wù)正在對一條記錄做修改嚼蚀,在這個事務(wù)并提交前禁灼,這條記錄的數(shù)據(jù)就處于不一致狀態(tài);這時轿曙,另一個事務(wù)也來讀取同一條記錄弄捕,如果不加控制,第二個事務(wù)讀取了這些“臟”的數(shù)據(jù)导帝,并據(jù)此做進一步的處理守谓,就會產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象地叫做“臟讀”您单。
- 不可重復讀(Non-Repeatable Reads):一個事務(wù)在讀取某些數(shù)據(jù)已經(jīng)發(fā)生了改變斋荞、或某些記錄已經(jīng)被刪除了!這種現(xiàn)象叫做“不可重復讀”虐秦。
- 幻讀(Phantom Reads):一個事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù)平酿,卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”悦陋。
3蜈彼、事務(wù)隔離級別
在并發(fā)事務(wù)處理帶來的問題中,“更新丟失”通常應(yīng)該是完全避免的俺驶。但防止更新丟失柳刮,并不能單靠數(shù)據(jù)庫事務(wù)控制器來解決,需要應(yīng)用程序?qū)σ碌臄?shù)據(jù)加必要的鎖來解決痒钝,因此秉颗,防止更新丟失應(yīng)該是應(yīng)用的責任。
“臟讀”送矩、“不可重復讀”和“幻讀”玩徊,其實都是數(shù)據(jù)庫讀一致性問題先馆,必須由數(shù)據(jù)庫提供一定的事務(wù)隔離機制來解決拄查。數(shù)據(jù)庫實現(xiàn)事務(wù)隔離的方式跟畅,基本可以分為以下兩種。
- 一種是在讀取數(shù)據(jù)前晌块,對其加鎖爱沟,阻止其他事務(wù)對數(shù)據(jù)進行修改。
- 另一種是不用加任何鎖匆背,通過一定機制生成一個數(shù)據(jù)請求時間點的一致性數(shù)據(jù)快照(Snapshot)呼伸,并用這個快照來提供一定級別(語句級或事務(wù)級)的一致性讀取。從用戶的角度钝尸,好像是數(shù)據(jù)庫可以提供同一數(shù)據(jù)的多個版本括享,因此搂根,這種技術(shù)叫做數(shù)據(jù)多版本并發(fā)控制(MultiVersion Concurrency Control,簡稱MVCC或MCC)铃辖,也經(jīng)常稱為多版本數(shù)據(jù)庫剩愧。
在MVCC并發(fā)控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)娇斩∪示恚快照讀,讀取的是記錄的可見版本 (有可能是歷史版本)犬第,不用加鎖锦积。當前讀,讀取的是記錄的最新版本瓶殃,并且,當前讀返回的記錄副签,都會加上鎖遥椿,保證其他事務(wù)不會再并發(fā)修改這條記錄。
在一個支持MVCC并發(fā)控制的系統(tǒng)中淆储,哪些讀操作是快照讀冠场?哪些操作又是當前讀呢?以MySQL InnoDB為例:
- 快照讀:簡單的select操作本砰,屬于快照讀碴裙,不加鎖。(當然点额,也有例外)
select * from table where ?;
- 當前讀:特殊的讀操作舔株,插入/更新/刪除操作,屬于當前讀还棱,需要加鎖载慈。
下面語句都屬于當前讀,讀取記錄的最新版本珍手。并且办铡,讀取之后,還需要保證其他并發(fā)事務(wù)不能修改當前記錄琳要,對讀取記錄加鎖寡具。其中,除了第一條語句稚补,對讀取記錄加S鎖 (共享鎖)外童叠,其他的操作,都加的是X鎖 (排它鎖)课幕。
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 ?;
數(shù)據(jù)庫的事務(wù)隔離越嚴格拯钻,并發(fā)副作用越小帖努,但付出的代價也就越大,因為事務(wù)隔離實質(zhì)上就是使事務(wù)在一定程度上 “串行化”進行粪般,這顯然與“并發(fā)”是矛盾的拼余。同時,不同的應(yīng)用對讀一致性和事務(wù)隔離程度的要求也是不同的亩歹,比如許多應(yīng)用對“不可重復讀”和“幻讀”并不敏 感匙监,可能更關(guān)心數(shù)據(jù)并發(fā)訪問的能力。
為了解決“隔離”與“并發(fā)”的矛盾小作,ISO/ANSI SQL92定義了4個事務(wù)隔離級別亭姥,每個級別的隔離程度不同,允許出現(xiàn)的副作用也不同顾稀,應(yīng)用可以根據(jù)自己的業(yè)務(wù)邏輯要求达罗,通過選擇不同的隔離級別來平衡 “隔離”與“并發(fā)”的矛盾。下表很好地概括了這4個隔離級別的特性静秆。
獲取InonoD行鎖爭用情況
可以通過檢查InnoDB_row_lock狀態(tài)變量來分析系統(tǒng)上的行鎖的爭奪情況:
mysql> show status like 'innodb_row_lock%';
[圖片上傳失敗...(image-29cf92-1571818201157)]
如果發(fā)現(xiàn)鎖爭用比較嚴重粮揉,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比較高,還可以通過設(shè)置InnoDB Monitors來進一步觀察發(fā)生鎖沖突的表抚笔、數(shù)據(jù)行等扶认,并分析鎖爭用的原因。
InnoDB的行鎖模式及加鎖方法
InnoDB實現(xiàn)了以下兩種類型的行鎖殊橙。
- 共享鎖(s):又稱讀鎖辐宾。允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖膨蛮。若事務(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ù)更新數(shù)據(jù)豺鼻,阻止其他事務(wù)取得相同的數(shù)據(jù)集共享讀鎖和排他寫鎖综液。若事務(wù)T對數(shù)據(jù)對象A加上X鎖,事務(wù)T可以讀A也可以修改A儒飒,其他事務(wù)不能再對A加任何鎖谬莹,直到T釋放A上的鎖。
- 對于共享鎖大家可能很好理解,就是多個事務(wù)只能讀數(shù)據(jù)不能改數(shù)據(jù)附帽。
對于排他鎖大家的理解可能就有些差別埠戳,我當初就犯了一個錯誤,以為排他鎖鎖住一行數(shù)據(jù)后蕉扮,其他事務(wù)就不能讀取和修改該行數(shù)據(jù)整胃,其實不是這樣的。排他鎖指的是一個事務(wù)在一行數(shù)據(jù)加上排他鎖后喳钟,其他事務(wù)不能再在其上加其他的鎖屁使。mysql InnoDB引擎默認的修改數(shù)據(jù)語句:update,delete,insert都會自動給涉及到的數(shù)據(jù)加上排他鎖,select語句默認不會加任何鎖類型奔则,如果加排他鎖可以使用select …for update語句蛮寂,加共享鎖可以使用select … lock in share mode語句。所以加過排他鎖的數(shù)據(jù)行在其他事務(wù)種是不能修改數(shù)據(jù)的易茬,也不能通過for update和lock in share mode鎖的方式查詢數(shù)據(jù)酬蹋,但可以直接通過select …from…查詢數(shù)據(jù),因為普通查詢沒有任何鎖機制抽莱。
另外范抓,為了允許行鎖和表鎖共存,實現(xiàn)多粒度鎖機制岸蜗,InnoDB還有兩種內(nèi)部使用的意向鎖(Intention Locks)尉咕,這兩種意向鎖都是表鎖叠蝇。
- 意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行共享鎖璃岳,事務(wù)在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。
- 意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加排他鎖悔捶,事務(wù)在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖铃慷。
InnoDB行鎖模式兼容性列表:
如果一個事務(wù)請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務(wù)蜕该;反之犁柜,如果兩者兩者不兼容,該事務(wù)就要等待鎖釋放堂淡。
意向鎖是InnoDB自動加的馋缅,不需用戶干預。對于UPDATE绢淀、DELETE和INSERT語句萤悴,InnoDB會自動給涉及數(shù)據(jù)集加排他鎖(X);對于普通SELECT語句皆的,InnoDB不會加任何鎖覆履。
事務(wù)可以通過以下語句顯式給記錄集加共享鎖或排他鎖:
- 共享鎖(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
。 - 排他鎖(X):
SELECT * FROM table_name WHERE ... FOR UPDATE
。
用SELECT ... IN SHARE MODE
獲得共享鎖硝全,主要用在需要數(shù)據(jù)依存關(guān)系時來確認某行記錄是否存在栖雾,并確保沒有人對這個記錄進行UPDATE或者DELETE操作。但是如果當前事務(wù)也需要對該記錄進行更新操作伟众,則很有可能造成死鎖析藕,對于鎖定行記錄后需要進行更新操作的應(yīng)用,應(yīng)該使用SELECT… FOR UPDATE方式獲得排他鎖赂鲤。
InnoDB行鎖實現(xiàn)方式
InnoDB行鎖是通過給索引上的索引項加鎖來實現(xiàn)的噪径,這一點MySQL與Oracle不同,后者是通過在數(shù)據(jù)塊中對相應(yīng)數(shù)據(jù)行加鎖來實現(xiàn)的数初。InnoDB這種行鎖實現(xiàn)特點意味著:只有通過索引條件檢索數(shù)據(jù)找爱,InnoDB才使用行級鎖,否則泡孩,InnoDB將使用表鎖车摄!
在實際應(yīng)用中,要特別注意InnoDB行鎖的這一特性仑鸥,不然的話吮播,可能導致大量的鎖沖突,從而影響并發(fā)性能眼俊。下面通過一些實際例子來加以說明意狠。
(1)在不通過索引條件查詢的時候,InnoDB確實使用的是表鎖疮胖,而不是行鎖环戈。
mysql> create table tab_no_index(id int,name varchar(10)) engine=innodb;
Query OK, 0 rows affected (0.15 sec)
mysql> insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
[圖片上傳失敗...(image-dd9888-1571818201157)]
在上面的例子中,看起來session_1只給一行加了排他鎖澎灸,但session_2在請求其他行的排他鎖時院塞,卻出現(xiàn)了鎖等待!原因就是在沒有索引的情況下性昭,InnoDB只能使用表鎖拦止。當我們給其增加一個索引后,InnoDB就只鎖定了符合條件的行糜颠,如下例所示:
創(chuàng)建tab_with_index表汹族,id字段有普通索引:
mysql> create table tab_with_index(id int,name varchar(10)) engine=innodb;
mysql> alter table tab_with_index add index id(id);
[圖片上傳失敗...(image-f99042-1571818201157)]
(2)由于MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖其兴,所以雖然是訪問不同行的記錄顶瞒,但是如果是使用相同的索引鍵,是會出現(xiàn)鎖沖突的忌警。應(yīng)用設(shè)計的時候要注意這一點搁拙。
在下面的例子中秒梳,表tab_with_index的id字段有索引,name字段沒有索引:
mysql> alter table tab_with_index drop index name;
Query OK, 4 rows affected (0.22 sec) Records: 4 Duplicates: 0
Warnings: 0
mysql> insert into tab_with_index values(1,'4');
Query OK, 1 row affected (0.00 sec)
mysql> select * from tab_with_index where id = 1;
InnoDB存儲引擎使用相同索引鍵的阻塞例子
(3)當表有多個索引的時候箕速,不同的事務(wù)可以使用不同的索引鎖定不同的行酪碘,另外,不論是使用主鍵索引盐茎、唯一索引或普通索引兴垦,InnoDB都會使用行鎖來對數(shù)據(jù)加鎖。
在下面的例子中字柠,表tab_with_index的id字段有主鍵索引探越,name字段有普通索引:
mysql> alter table tab_with_index add index name(name);
Query OK, 5 rows affected (0.23 sec) Records: 5 Duplicates: 0
Warnings: 0
InnoDB存儲引擎的表使用不同索引的阻塞例子
(4)即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由MySQL通過判斷不同執(zhí)行計劃的代價來決 定的窑业,如果MySQL認為全表掃描效率更高钦幔,比如對一些很小的表,它就不會使用索引常柄,這種情況下InnoDB將使用表鎖鲤氢,而不是行鎖。因此西潘,在分析鎖沖突 時卷玉,別忘了檢查SQL的執(zhí)行計劃,以確認是否真正使用了索引喷市。
比如相种,在tab_with_index表里的name字段有索引,但是name字段是varchar類型的品姓,檢索值的數(shù)據(jù)類型與索引字段不同寝并,雖然MySQL能夠進行數(shù)據(jù)類型轉(zhuǎn)換,但卻不會使用索引缭黔,從而導致InnoDB使用表鎖食茎。通過用explain檢查兩條SQL的執(zhí)行計劃蒂破,我們可以清楚地看到了這一點馏谨。
mysql> explain select * from tab_with_index where name = 1 \G
mysql> explain select * from tab_with_index where name = '1' \G
間隙鎖(Next-Key鎖)
當我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請求共享或排他鎖時附迷,InnoDB會給符合條件的已有數(shù)據(jù)記錄的 索引項加鎖惧互;對于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”喇伯,InnoDB也會對這個“間隙”加鎖喊儡,這種鎖機制就是所謂的間隙鎖 (Next-Key鎖)。
舉例來說稻据,假如emp表中只有101條記錄艾猜,其empid的值分別是 1,2,…,100,101,下面的SQL:
Select * from emp where empid > 100 for update;
是一個范圍條件的檢索,InnoDB不僅會對符合條件的empid值為101的記錄加鎖匆赃,也會對empid大于101(這些記錄并不存在)的“間隙”加鎖淤毛。
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀算柳,以滿足相關(guān)隔離級別的要求低淡,對于上面的例子,要是不使 用間隙鎖瞬项,如果其他事務(wù)插入了empid大于100的任何記錄蔗蹋,那么本事務(wù)如果再次執(zhí)行上述語句,就會發(fā)生幻讀囱淋;另外一方面猪杭,是為了滿足其恢復和復制的需 要。有關(guān)其恢復和復制對鎖機制的影響妥衣,以及不同隔離級別下InnoDB使用間隙鎖的情況胁孙,在后續(xù)的章節(jié)中會做進一步介紹。
很顯然称鳞,在使用范圍條件檢索并鎖定記錄時涮较,InnoDB這種加鎖機制會阻塞符合條件范圍內(nèi)鍵值的并發(fā)插入,這往往會造成嚴重的鎖等待冈止。因此狂票,在實際應(yīng)用開發(fā)中,尤其是并發(fā)插入比較多的應(yīng)用熙暴,我們要盡量優(yōu)化業(yè)務(wù)邏輯闺属,盡量使用相等條件來訪問更新數(shù)據(jù),避免使用范圍條件周霉。
還要特別說明的是掂器,InnoDB除了通過范圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖俱箱,InnoDB也會使用間隙鎖国瓮!下面這個例子假設(shè)emp表中只有101條記錄,其empid的值分別是1,2,……,100,101狞谱。
InnoDB存儲引擎的間隙鎖阻塞例子
小結(jié)
本文重點介紹了MySQL中MyISAM表級鎖和InnoDB行級鎖的實現(xiàn)特點乃摹,并討論了兩種存儲引擎經(jīng)常遇到的鎖問題和解決辦法。
對于MyISAM的表鎖跟衅,主要討論了以下幾點:
(1)共享讀鎖(S)之間是兼容的孵睬,但共享讀鎖(S)與排他寫鎖(X)之間,以及排他寫鎖(X)之間是互斥的伶跷,也就是說讀和寫是串行的掰读。
(2)在一定條件下秘狞,MyISAM允許查詢和插入并發(fā)執(zhí)行,我們可以利用這一點來解決應(yīng)用中對同一表查詢和插入的鎖爭用問題蹈集。
(3)MyISAM默認的鎖調(diào)度機制是寫優(yōu)先谒撼,這并不一定適合所有應(yīng)用,用戶可以通過設(shè)置LOW_PRIORITY_UPDATES參數(shù)雾狈,或在INSERT廓潜、UPDATE、DELETE語句中指定LOW_PRIORITY選項來調(diào)節(jié)讀寫鎖的爭用善榛。
(4)由于表鎖的鎖定粒度大辩蛋,讀寫之間又是串行的,因此移盆,如果更新操作較多悼院,MyISAM表可能會出現(xiàn)嚴重的鎖等待,可以考慮采用InnoDB表來減少鎖沖突咒循。
對于InnoDB表据途,本文主要討論了以下幾項內(nèi)容:
(1)InnoDB的行鎖是基于索引實現(xiàn)的,如果不通過索引訪問數(shù)據(jù)叙甸,InnoDB會使用表鎖颖医。
(2)介紹了InnoDB間隙鎖(Next-key)機制,以及InnoDB使用間隙鎖的原因裆蒸。
在不同的隔離級別下熔萧,InnoDB的鎖機制和一致性讀策略不同。
在了解InnoDB鎖特性后僚祷,用戶可以通過設(shè)計和SQL調(diào)整等措施減少鎖沖突和死鎖佛致,包括:
- 盡量使用較低的隔離級別; 精心設(shè)計索引辙谜,并盡量使用索引訪問數(shù)據(jù)俺榆,使加鎖更精確,從而減少鎖沖突的機會装哆;
- 選擇合理的事務(wù)大小罐脊,小事務(wù)發(fā)生鎖沖突的幾率也更小烂琴;
- 給記錄集顯式加鎖時爹殊,最好一次性請求足夠級別的鎖蜕乡。比如要修改數(shù)據(jù)的話奸绷,最好直接申請排他鎖,而不是先申請共享鎖层玲,修改時再請求排他鎖号醉,這樣容易產(chǎn)生死鎖反症;
- 不同的程序訪問一組表時,應(yīng)盡量約定以相同的順序訪問各表畔派,對一個表而言铅碍,盡可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機會线椰;
- 盡量用相等條件訪問數(shù)據(jù)胞谈,這樣可以避免間隙鎖對并發(fā)插入的影響; 不要申請超過實際需要的鎖級別憨愉;除非必須烦绳,查詢時不要顯示加鎖;
- 對于一些特定的事務(wù)配紫,可以使用表鎖來提高處理速度或減少死鎖的可能径密。