MySQL之事务、锁
鎖
一、概念
鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程訪問(wèn)某一個(gè)資源的機(jī)制。在數(shù)據(jù)庫(kù)中,除傳統(tǒng)的計(jì)算資源(CPU、RAM、IO)的爭(zhēng)用意外,數(shù)據(jù)也是一種許多用戶共享的資源。如何保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性、有效性是所有數(shù)據(jù)庫(kù)必須解決的一個(gè)問(wèn)題,鎖沖突也是影響數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)的一個(gè)重要因素。從這個(gè)角度來(lái)說(shuō),鎖對(duì)數(shù)據(jù)庫(kù)尤其重要,也更加復(fù)雜。本文就以MyISAM和InnoDB兩個(gè)引擎來(lái)說(shuō)明鎖的問(wèn)題;
二、MySQL鎖概述
相對(duì)其他數(shù)據(jù)庫(kù)而言,MySQL的鎖機(jī)制比較簡(jiǎn)單,其中最為顯著的特點(diǎn)是不同的引擎具有不同的鎖。比如MyISAM和InnoDB分別采用表鎖、行鎖,但是InnoDB也支持表鎖,默認(rèn)情況下采用行鎖;
三、MySQL表鎖和行鎖對(duì)比
| 鎖的名稱(chēng) | 開(kāi)銷(xiāo) | 加鎖速度 | 死鎖 | 粒度 | 并發(fā)性能 |
| 表鎖 | 小 | 快 | 不會(huì)出現(xiàn) | 大 | 最低 |
| 行鎖 | 大 | 慢 | 出現(xiàn) | 小 | 最高 |
從鎖的角度來(lái)說(shuō)表鎖適合查詢(xún)?yōu)橹?#xff0c;只有少量按索引條件更新數(shù)據(jù)的應(yīng)用;行鎖適合于大量按索引條件來(lái)并發(fā)更新少量的數(shù)據(jù),同時(shí)又有并發(fā)查詢(xún)的應(yīng)用;
MyISAM表鎖
?一、查詢(xún)表級(jí)鎖的爭(zhēng)用情況
可以通過(guò)table_locks_waited和table_locks_immediate來(lái)分析系統(tǒng)表鎖的爭(zhēng)奪;
如果table_locks_waited的值比較高的話,則說(shuō)明存在較高的表鎖爭(zhēng)奪。
二、表鎖的鎖模式
表級(jí)鎖有兩種方式:讀鎖和寫(xiě)鎖,鎖模式的兼容性如下:
| ?請(qǐng)求鎖模式是否兼容當(dāng)前鎖模式 | ?讀鎖 | ?寫(xiě)鎖 |
| ?讀鎖 | ?是 | ?否 |
| ?寫(xiě)鎖 | ?否 | ?否 |
由上表可見(jiàn),MyISAM表的讀操作是不會(huì)阻塞其他用戶對(duì)同一張表的讀請(qǐng)求,但會(huì)阻塞表的寫(xiě)請(qǐng)求;對(duì)于MyISAM的寫(xiě)操作,則會(huì)阻塞其他用戶對(duì)同一張表的讀和寫(xiě)操作。MyISAM的讀和寫(xiě)操作之間,寫(xiě)操作和寫(xiě)操作之間是串行的。也就是說(shuō)當(dāng)一個(gè)線程獲得一個(gè)表的寫(xiě)鎖之后,只有持有鎖的線程可以執(zhí)行更新操作,其他線程的讀和寫(xiě)都要等待,直到鎖被釋放為止。
三、如何加表鎖
MyISAM在執(zhí)行查詢(xún)語(yǔ)句(SELECT)前,會(huì)自動(dòng)給涉及的所有表加讀鎖,在執(zhí)行更新操作(UPDATE、DELETE、INSERT等)前,會(huì)自動(dòng)給涉及的表加寫(xiě)鎖,這個(gè)過(guò)程并不需要用戶干預(yù),因此,用戶一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖。
給MyISAM表加顯示鎖,一般是在一定程度上模擬事務(wù)操作,實(shí)現(xiàn)某一時(shí)間點(diǎn)多個(gè)表大的一致性讀取。例如我們現(xiàn)在有一個(gè)庫(kù)存表和入庫(kù)單表,我們要判斷入庫(kù)單的總數(shù)是否和庫(kù)存表的一致,這個(gè)時(shí)候我們不給這兩個(gè)表加鎖,就可能產(chǎn)生錯(cuò)誤的結(jié)果,比如在執(zhí)行第一次查詢(xún)庫(kù)存的時(shí)候,入庫(kù)單表發(fā)生了改變。這個(gè)時(shí)候就造成數(shù)據(jù)錯(cuò)誤。
對(duì)以上的列子做出總結(jié):
1、上面的例子在LOCK TABLES時(shí)加了“l(fā)ocal”選項(xiàng),其作用就是在滿足MyISAM表并發(fā)插入條件的情況下,允許其他用戶在表尾并發(fā)插入記錄;
2、在用LOCK TABLES給表顯式加表鎖時(shí),必須同時(shí)取得所有涉及到表的鎖,并且MySQL不支持鎖升級(jí)。也就是說(shuō),在執(zhí)行LOCK TABLES后,只能訪問(wèn)顯式加鎖的這些表,不能訪問(wèn)未加鎖的表;同時(shí),如果加的是讀鎖,那么只能執(zhí)行查詢(xún)操作,而不能執(zhí)行更新操作。其實(shí),在自動(dòng)加鎖的情況下也基本如此,MyISAM總是一次獲得SQL語(yǔ)句所需要的全部鎖。這也正是MyISAM表不會(huì)出現(xiàn)死鎖的原因。
四、并發(fā)插入
MyISAM的存儲(chǔ)引擎有一個(gè)系統(tǒng)的變量concurrent_insert,專(zhuān)門(mén)用于控制并發(fā)插入的行為,其值分別是0,1,2:
1、?當(dāng)concurrent_insert設(shè)置為0時(shí),不允許并發(fā)插入。
2、?當(dāng)concurrent_insert設(shè)置為1時(shí),如果MyISAM表中沒(méi)有空洞(即表的中間沒(méi)有被刪除的行),MyISAM允許在一個(gè)進(jìn)程讀表的同時(shí),另一個(gè)進(jìn)程從表尾插入記錄。這也是MySQL的默認(rèn)設(shè)置。
3、當(dāng)concurrent_insert設(shè)置為2時(shí),無(wú)論MyISAM表中有沒(méi)有空洞,都允許在表尾并發(fā)插入記錄。
五、MyISAM的鎖調(diào)度
MyISAM的存儲(chǔ)引擎的讀鎖和寫(xiě)鎖互斥的,讀寫(xiě)操作都是串行的。比如現(xiàn)在有一個(gè)進(jìn)程某一個(gè)表的讀鎖,這是同時(shí)有個(gè)進(jìn)程來(lái)請(qǐng)求同一張表的寫(xiě)鎖,這個(gè)時(shí)候MyISAM表會(huì)怎么做呢?結(jié)果就是寫(xiě)鎖先獲取,不僅如此,如果讀鎖先進(jìn)入隊(duì)列,寫(xiě)鎖后進(jìn)入隊(duì)列,這個(gè)時(shí)候也是寫(xiě)鎖現(xiàn)貨的。這是因?yàn)镸ySQL認(rèn)為寫(xiě)比讀重要。這也正是MyISAM表不太合適有大量更新操作和查詢(xún)操作的原因。因?yàn)榇罅康母虏僮魇堑淖x操作難以獲得鎖,從而可能永遠(yuǎn)阻塞,這種情況可能會(huì)變的非常糟糕。但是我們可以設(shè)置MyISAM的調(diào)度行為
1、 通過(guò)指定啟動(dòng)參數(shù)low-priority-updates,使MyISAM引擎默認(rèn)給予讀請(qǐng)求以?xún)?yōu)先的權(quán)利。 2、通過(guò)執(zhí)行命令SET LOW_PRIORITY_UPDATES=1,使該連接發(fā)出的更新請(qǐng)求優(yōu)先級(jí)降低。 3、通過(guò)指定INSERT、UPDATE、DELETE語(yǔ)句的LOW_PRIORITY屬性,降低該語(yǔ)句的優(yōu)先級(jí)。 4、給系統(tǒng)參數(shù)max_write_lock_count設(shè)置一個(gè)合適的值,當(dāng)一個(gè)表的讀鎖達(dá)到這個(gè)值后,MySQL就暫時(shí)將寫(xiě)請(qǐng)求的優(yōu)先級(jí)降低,給讀進(jìn)程一定獲得鎖的機(jī)會(huì)。 5、一些需要長(zhǎng)時(shí)間運(yùn)行的查詢(xún)操作,也會(huì)使寫(xiě)進(jìn)程阻塞。六、什么時(shí)候用表鎖
1、事務(wù)需要更新大部分或全部數(shù)據(jù),表又比較大,如果使用默認(rèn)的行鎖,不僅這個(gè)事務(wù)執(zhí)行效率低,而且可能造成其他事務(wù)長(zhǎng)時(shí)間鎖等待和鎖沖突,這種情況下可以考慮使用表鎖來(lái)提高該事務(wù)的執(zhí)行速度。
2、事務(wù)涉及多個(gè)表,比較復(fù)雜,很可能引起死鎖,造成大量事務(wù)回滾。這種情況也可以考慮一次性鎖定事務(wù)涉及的表,從而避免死鎖、減少數(shù)據(jù)庫(kù)因事務(wù)回滾帶來(lái)的開(kāi)銷(xiāo)。
InnoDB鎖
InnoDB和MyISAM的區(qū)別是,InnoDB支持事務(wù)和行級(jí)鎖。行級(jí)鎖和表鎖有很多不同的地方,事務(wù)的引入也帶來(lái)了一些新的問(wèn)題。
一、事務(wù)
1、事務(wù)的特性
原子性:對(duì)數(shù)據(jù)的修改,要么全部成功,要么全部失敗。
一致性:在事務(wù)開(kāi)始的時(shí)候和完成的時(shí)候,數(shù)據(jù)都必須保持一致的狀態(tài)。
隔離性:保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行。也就是事務(wù)處理過(guò)程的中間部分是不可見(jiàn)的。
持久性:事務(wù)完成之后,他對(duì)數(shù)據(jù)的修改是永久的,即系統(tǒng)出現(xiàn)故障也可以保持。
2、并發(fā)事務(wù)帶來(lái)的問(wèn)題
更新丟失:最后的更新覆蓋了其他事務(wù)所做的更新。解決此問(wèn)題的條件是,當(dāng)前用戶在沒(méi)有提交事務(wù)的時(shí)候,其他用戶是不可以操作的。
臟讀:一個(gè)事務(wù)正在對(duì)一條記錄做修改,這個(gè)事務(wù)在完成提交前,這個(gè)時(shí)候另一個(gè)事務(wù)來(lái)讀取這一行的數(shù)據(jù),就有可能讀到臟數(shù)據(jù)。
不可重復(fù)讀:一個(gè)事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間,再次讀取以前讀過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!
幻度:一個(gè)事務(wù)按相同的查詢(xún)條件重新讀取以前檢索過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢(xún)條件的新數(shù)據(jù)。
3、事務(wù)的隔離級(jí)別
a、更新丟失通常應(yīng)該完全可以避免,但防止更新丟失,不能單靠數(shù)據(jù)庫(kù)事務(wù)控制來(lái)解決,需要應(yīng)用程序?qū)Ω碌臄?shù)據(jù)加鎖來(lái)解決;
b、“臟讀”、“不可重復(fù)讀”和“幻讀”,其實(shí)都是數(shù)據(jù)庫(kù)讀一致性問(wèn)題,必須由數(shù)據(jù)庫(kù)提供一定的事務(wù)隔離機(jī)制來(lái)解決
| 隔離級(jí)別 | 讀數(shù)據(jù)一致性 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
| 未提交讀(Read uncommitted) | 最低級(jí)別,只能保證不讀取物理上損壞的數(shù)據(jù) | 是 | 是 | 是 |
| 已提交度(Read committed) | 語(yǔ)句級(jí) | 否 | 是 | 是 |
| 可重復(fù)讀(Repeatable read) | 事務(wù)級(jí) | 否 | 否 | 是 |
| 可序列化(Serializable) | 最高級(jí)別,事務(wù)級(jí) | 否 | 否 | 否 |
4、InnoDB的行鎖模式和加鎖方法
| 鎖類(lèi)型 | 可讀(其他事務(wù)) | 可寫(xiě)(其他事務(wù)) |
| 共享鎖(S) | 是 | 否 |
| 排他鎖(x) | 否 | 否 |
?
為了行鎖和表鎖共從,InnoDB又提供了兩個(gè)意向鎖(也就是表鎖)
意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
這四種鎖之間的兼容性如下:
| 鎖模式 | S | X | IS | IX |
| S | 兼容 | 沖突 | 兼容 | 沖突 |
| X | 沖突 | 沖突 | 沖突 | 沖突 |
| IS | 兼容 | 沖突? | 兼容 | 兼容 |
| IX | ?沖突 | ?沖突 | 兼容? | ?兼容 |
如果一個(gè)事務(wù)請(qǐng)求的鎖模式與當(dāng)前的鎖兼容,InnoDB就將請(qǐng)求的鎖授予該事務(wù);反之,如果兩者不兼容,該事務(wù)就要等待鎖釋放。
意向鎖是InnoDB自動(dòng)加的,不需用戶干預(yù)。對(duì)于UPDATE、DELETE和INSERT語(yǔ)句,InnoDB會(huì)自動(dòng)給涉及數(shù)據(jù)集加排他鎖(X);對(duì)于普通SELECT語(yǔ)句,InnoDB不會(huì)加任何鎖;5、InnoDB行鎖的實(shí)現(xiàn)方式
InnoDB的行鎖實(shí)現(xiàn)是通過(guò)給索引上的索引項(xiàng)加鎖來(lái)實(shí)現(xiàn)的;也就說(shuō)只有通過(guò)索引檢索數(shù)據(jù),InnoDB才使用行級(jí)鎖,否則InnoDB將使用表鎖。
注意點(diǎn):
a、在不通過(guò)索引條件查詢(xún)的時(shí)候,InnoDB確實(shí)使用的是表鎖,而不是行鎖。
b、由于MySQL的行鎖是針對(duì)索引加的鎖,不是針對(duì)記錄加的鎖,所以雖然是訪問(wèn)不同行的記錄,但是如果是使用相同的索引鍵,是會(huì)出現(xiàn)鎖沖突的。
c、當(dāng)表有多個(gè)索引的時(shí)候,不同的事務(wù)可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會(huì)使用行鎖來(lái)對(duì)數(shù)據(jù)加鎖。
d、即便在條件中使用了索引字段,但是否使用索引來(lái)檢索數(shù)據(jù)是由MySQL通過(guò)判斷不同執(zhí)行計(jì)劃的代價(jià)來(lái)決定的,如果MySQL認(rèn)為全表掃描效率更高,比如對(duì)一些很小的表,它就不會(huì)使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時(shí),別忘了檢查SQL的執(zhí)行計(jì)劃,以確認(rèn)是否真正使用了索引。
6、間隙鎖
當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請(qǐng)求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)。
目的
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關(guān)隔離級(jí)別的要求,如果不使用間隙鎖,如果其他事務(wù)插入了超過(guò)范圍的任何記錄,那么本事務(wù)如果再次執(zhí)行上述語(yǔ)句,就會(huì)發(fā)生幻讀;另外一方面,是為了滿足其恢復(fù)和復(fù)制的需要。
很顯然,在使用范圍條件檢索并鎖定記錄時(shí),InnoDB這種加鎖機(jī)制會(huì)阻塞符合條件范圍內(nèi)鍵值的并發(fā)插入,這往往會(huì)造成嚴(yán)重的鎖等待。因此,在實(shí)際應(yīng)用開(kāi)發(fā)中,尤其是并發(fā)插入比較多的應(yīng)用,我們要盡量?jī)?yōu)化業(yè)務(wù)邏輯,盡量使用相等條件來(lái)訪問(wèn)更新數(shù)據(jù),避免使用范圍條件。
死鎖
發(fā)生死鎖后,InnoDB一般都能自動(dòng)檢測(cè)到,并使一個(gè)事務(wù)釋放鎖并回退,另一個(gè)事務(wù)獲得鎖,繼續(xù)完成事務(wù)。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB并不能完全自動(dòng)檢測(cè)到死鎖,這需要通過(guò)設(shè)置鎖等待超時(shí)參數(shù) innodb_lock_wait_timeout來(lái)解決。需要說(shuō)明的是,這個(gè)參數(shù)并不是只用來(lái)解決死鎖問(wèn)題,在并發(fā)訪問(wèn)比較高的情況下,如果大量事務(wù)因無(wú)法立即獲得所需的鎖而掛起,會(huì)占用大量計(jì)算機(jī)資源,造成嚴(yán)重性能問(wèn)題,甚至拖跨數(shù)據(jù)庫(kù)。我們通過(guò)設(shè)置合適的鎖等待超時(shí)閾值,可以避免這種情況發(fā)生。
通常來(lái)說(shuō),死鎖都是應(yīng)用設(shè)計(jì)的問(wèn)題,通過(guò)調(diào)整業(yè)務(wù)流程、數(shù)據(jù)庫(kù)對(duì)象設(shè)計(jì)、事務(wù)大小,以及訪問(wèn)數(shù)據(jù)庫(kù)的SQL語(yǔ)句,絕大部分死鎖都可以避免。下面就通過(guò)實(shí)例來(lái)介紹幾種避免死鎖的常用方法。
1、在應(yīng)用中,如果不同的程序會(huì)并發(fā)存取多個(gè)表,應(yīng)盡量約定以相同的順序來(lái)訪問(wèn)表,這樣可以大大降低產(chǎn)生死鎖的機(jī)會(huì)。
2、在程序以批量方式處理數(shù)據(jù)的時(shí)候,如果事先對(duì)數(shù)據(jù)排序,保證每個(gè)線程按固定的順序來(lái)處理記錄,也可以大大降低出現(xiàn)死鎖的可能。
3、在事務(wù)中,如果要更新記錄,應(yīng)該直接申請(qǐng)足夠級(jí)別的鎖,即排他鎖,而不應(yīng)先申請(qǐng)共享鎖,更新時(shí)再申請(qǐng)排他鎖,因?yàn)楫?dāng)用戶申請(qǐng)排他鎖時(shí),其他事務(wù)可能又已經(jīng)獲得了相同記錄的共享鎖,從而造成鎖沖突,甚至死鎖。
4、在REPEATABLE-READ隔離級(jí)別下,如果兩個(gè)線程同時(shí)對(duì)相同條件記錄用SELECT...FOR UPDATE加排他鎖,在沒(méi)有符合該條件記錄情況下,兩個(gè)線程都會(huì)加鎖成功。程序發(fā)現(xiàn)記錄尚不存在,就試圖插入一條新記錄,如果兩個(gè)線程都這么做,就會(huì)出現(xiàn)死鎖。這種情況下,將隔離級(jí)別改成READ COMMITTED,就可避免問(wèn)題
5、當(dāng)隔離級(jí)別為READ COMMITTED時(shí),如果兩個(gè)線程都先執(zhí)行SELECT...FOR UPDATE,判斷是否存在符合條件的記錄,如果沒(méi)有,就插入記錄。此時(shí),只有一個(gè)線程能插入成功,另一個(gè)線程會(huì)出現(xiàn)鎖等待,當(dāng)?shù)?個(gè)線程提交后,第2個(gè)線程會(huì)因主鍵重出錯(cuò),但雖然這個(gè)線程出錯(cuò)了,卻會(huì)獲得一個(gè)排他鎖!這時(shí)如果有第3個(gè)線程又來(lái)申請(qǐng)排他鎖,也會(huì)出現(xiàn)死鎖。
轉(zhuǎn)載于:https://www.cnblogs.com/meichao/p/9263153.html
總結(jié)
以上是生活随笔為你收集整理的MySQL之事务、锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: tencent intern learn
- 下一篇: oracle 数据库,用户管理以及表空间