一文詳解MySQL的并發(fā)控制
1.概述
所謂的并發(fā)控制,就是規(guī)避多個(gè)會(huì)話并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)帶來(lái)的諸如臟數(shù)據(jù)之類的數(shù)據(jù)一致性問(wèn)題,MySQL中提供了一系列的機(jī)制讓我們可以去進(jìn)行并發(fā)控制。
本質(zhì)上來(lái)說(shuō)MySQL就是用的兩種鎖來(lái)進(jìn)行并發(fā)控制,一種是表鎖,鎖住整張表;一種是行鎖,鎖住某個(gè)數(shù)據(jù)行。
平時(shí)我們使用的時(shí)候,很少會(huì)直接去操作鎖,因?yàn)镸ySQL已經(jīng)幫我們封裝的很好了,直接用innodb引擎+事務(wù)就能很好的進(jìn)行并發(fā)控制,事務(wù)底層其實(shí)依賴的就是行鎖。
本文會(huì)先聊事務(wù)、再聊表鎖、行鎖,但其實(shí)總的來(lái)說(shuō)MySQL進(jìn)行并發(fā)控制,就是行鎖和表鎖,事務(wù)的底層用的就是行鎖,只是事務(wù)太重要了所以單獨(dú)拎出來(lái)作為一個(gè)獨(dú)立的章節(jié)聊。
2.事務(wù)
2.1.什么是事務(wù)
注意:只有innodb引擎是支持事務(wù)的,所以本文與事務(wù)相關(guān)的討論,默認(rèn)都是在innodb引擎下。
在實(shí)際使用中,會(huì)存在這樣一類場(chǎng)景,我們希望幾條SQL要么同時(shí)執(zhí)行成功,要么同時(shí)失敗,不能有的成功,有的失敗。
比如網(wǎng)購(gòu)下單,生成訂單、扣減庫(kù)存兩條SQL,必須保證要么全部成功,要么全部失敗,不能說(shuō)生成訂單成功,但是扣減庫(kù)存失敗了,或者說(shuō)扣減庫(kù)存成功但是生成訂單失敗了,以上任何一種情況都是會(huì)產(chǎn)生臟數(shù)據(jù)的。如果兩者中有一者失敗,另外一個(gè)也需要跟著執(zhí)行不成功,從而保證數(shù)據(jù)的正確性。
事務(wù)就是為了滿足將多條捆綁在一起,同成功,同失敗而出現(xiàn)的。人們?cè)趯?shí)現(xiàn)事務(wù)過(guò)程中,發(fā)現(xiàn)事務(wù)要實(shí)現(xiàn)上面我們說(shuō)的效果,那么就必須實(shí)現(xiàn)四點(diǎn):
- 原子性(Atomicity)
- 一致性(Consistent)
- 隔離性(Isolation)
- 持久性(Durable)
也就是大名鼎鼎的ACID,很多地方稱其為事務(wù)的四大特性。
1.原子性:
事務(wù)是一個(gè)原子操作單元,其對(duì)數(shù)據(jù)的修改,要么全部執(zhí)行,要么全部不執(zhí)行。
2.一致性:
數(shù)據(jù)庫(kù)中的數(shù)據(jù)總是從一個(gè)狀態(tài)到另一個(gè)狀態(tài),不能存在中間狀態(tài)。繼續(xù)以網(wǎng)購(gòu)下單為例,一開(kāi)始的數(shù)據(jù)庫(kù)中的數(shù)據(jù)為A狀態(tài),執(zhí)行完事務(wù),扣減庫(kù)存、新增訂單后的數(shù)據(jù)狀態(tài)為B狀態(tài),數(shù)據(jù)庫(kù)只能由A到B,不能出現(xiàn)諸如扣了庫(kù)存,沒(méi)生成訂單,或者生成了訂單,沒(méi)扣庫(kù)存這樣的中間狀態(tài)。
3.隔離性:
一個(gè)事務(wù)在提交之前,其所做的修改對(duì)其它事務(wù)來(lái)說(shuō)是不可見(jiàn)的。不保證隔離性會(huì)產(chǎn)生臟數(shù)據(jù),這個(gè)很好理解,舉個(gè)例子:
A的銀行賬戶有400,有兩個(gè)事務(wù),彼此之間數(shù)據(jù)可見(jiàn),也就是一個(gè)事務(wù)修改數(shù)據(jù),不管提沒(méi)提交其他事務(wù)都看得見(jiàn)。
事務(wù)1,A向B轉(zhuǎn)200:
- A的賬戶扣減200
- B的賬戶新增200
事務(wù)2,A向C轉(zhuǎn)200:
- A的賬戶扣減200
- C的賬戶新增200
如果在事務(wù)1中的1、2步的時(shí)間間隙內(nèi)事務(wù)2間插執(zhí)行完畢,那么在事務(wù)1第2步執(zhí)行前A的賬戶中已經(jīng)被扣減了兩次200,余額為0,C的賬戶中多了200。
這時(shí)候如果在事務(wù)1的第2步中出錯(cuò)了、回滾,那么A的賬戶又會(huì)回到事務(wù)1第一步執(zhí)行之前的狀態(tài),也就是A的賬戶又恢復(fù)成了400。最后C的賬戶平白無(wú)故多了200。
4.持久性:
事務(wù)完成后,其對(duì)數(shù)據(jù)的修改是永久的,即使系統(tǒng)斷電、重啟,也不會(huì)變。
2.2.事務(wù)的隔離級(jí)別
2.2.1.三種數(shù)據(jù)一致性問(wèn)題
在沒(méi)有隔離性的情況下,事物之間會(huì)出現(xiàn)3種數(shù)據(jù)一致性問(wèn)題:
- 臟讀:事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù),然后B回滾操作,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)
- 不可重復(fù)讀:事務(wù) A 多次讀取同一數(shù)據(jù),事務(wù) B 在事務(wù)A多次讀取的過(guò)程中,對(duì)數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時(shí),結(jié)果 不一致。
- 幻讀:系統(tǒng)管理員A將數(shù)據(jù)庫(kù)中所有學(xué)生的成績(jī)從具體分?jǐn)?shù)改為ABCDE等級(jí),但是系統(tǒng)管理員B就在這個(gè)時(shí)候插入了一條具體分?jǐn)?shù)的記錄,當(dāng)系統(tǒng)管理員A改結(jié)束后發(fā)現(xiàn)還有一條記錄沒(méi)有改過(guò)來(lái),就好像發(fā)生了幻覺(jué)一樣,這就叫幻讀。
2.2.2.四種隔離級(jí)別
隔離級(jí)別可理解為,隔離性的嚴(yán)格度,MySQL并不是固定死了各個(gè)事務(wù)間就是不可讀的,而是規(guī)定了各種強(qiáng)度的隔離級(jí)別。
觀察上面3種數(shù)據(jù)一致性問(wèn)題就會(huì)發(fā)現(xiàn),解決它們需要的隔離性是遞增的,MySQL一共給出4種隔離級(jí)別,隔離性也是遞增的,對(duì)應(yīng)解決以上3個(gè)問(wèn)題,有3種,加上1種3種問(wèn)題都能覆蓋解決的:
- Read Uncommitted(讀未提交):最低的隔離級(jí)別,允許一個(gè)事務(wù)讀取另一個(gè)事務(wù)尚未提交的數(shù)據(jù)變更??赡軐?dǎo)致臟讀(Dirty Read)問(wèn)題。
- Read Committed(讀已提交):確保一個(gè)事務(wù)只能讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù)變更。防止臟讀問(wèn)題,但可能導(dǎo)致不可重復(fù)讀(Non-repeatable Read)問(wèn)題。
- Repeatable Read(可重復(fù)讀):確保在同一個(gè)事務(wù)中多次讀取同一數(shù)據(jù)時(shí),能夠得到一致的結(jié)果。防止臟讀和不可重復(fù)讀問(wèn)題,但可能導(dǎo)致幻讀(Phantom Read)問(wèn)題。
- Serializable(串行化):最高的隔離級(jí)別,強(qiáng)制事務(wù)串行執(zhí)行,確保不會(huì)發(fā)生臟讀、不可重復(fù)讀和幻讀問(wèn)題。但是并發(fā)性能較差,通常不建議在高并發(fā)環(huán)境中使用。
2.3.如何設(shè)置隔離級(jí)別
可以在連接字符串中設(shè)置隔離級(jí)別:
jdbc:mysql://localhost/mydatabase?useSSL=false&characterEncoding=utf8&transactionIsolation=隔離級(jí)別
可以通過(guò)SQL在會(huì)話中設(shè)置隔離級(jí)別:
SET TRANSACTION ISOLATION LEVEL 隔離級(jí)別;
3.鎖
3.1.鎖與事務(wù)的關(guān)系
鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問(wèn)某一資源的機(jī)制,用來(lái)解決并發(fā)訪問(wèn)帶來(lái)的數(shù)據(jù)一致性問(wèn)題。在數(shù)據(jù)庫(kù)中,除傳統(tǒng)的計(jì)算資源(如CPU、IO、RAM)的爭(zhēng)用以外,數(shù)據(jù)更是會(huì)被并發(fā)訪問(wèn)的資源。所以MySQL數(shù)據(jù)庫(kù)也用鎖機(jī)制來(lái)保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性。
不同的隔離級(jí)別底層就是用不同的鎖來(lái)實(shí)現(xiàn)的。
3.2.分類
MySQL的鎖可以從兩種維度來(lái)分,
一個(gè)維度是按照鎖的是一行,還是鎖的是整張表,分為:
- 行鎖
- 表鎖
另一個(gè)維度是按照鎖的操作是讀操作,還是寫(xiě)操作,分為:
- 讀鎖
- 寫(xiě)鎖
3.3.表鎖
3.3.1.概述
MySQL中innodb引擎和myisam引擎均支持用lock tables指令來(lái)鎖表。
3.3.2.讀鎖
讀鎖,一種共享鎖,針對(duì)被鎖表,所有會(huì)話都可以進(jìn)行讀操作,所有會(huì)話都無(wú)法進(jìn)行寫(xiě)操作。加鎖方和其他客戶端的區(qū)別是,加鎖方直接不允許進(jìn)行寫(xiě)操作,而其他會(huì)話的寫(xiě)操作允許進(jìn)行,只是會(huì)被阻塞掛起。鎖解開(kāi)后,所有掛起的操作線程會(huì)去重新?tīng)?zhēng)搶資源。
加鎖指令:
lock tables 表名 read;
釋放鎖指令:
unlock tanles;
加鎖方不允許進(jìn)行寫(xiě)操作:

其它客戶端的寫(xiě)操作在加鎖方釋放鎖之前都被掛起:

3.3.3.寫(xiě)鎖
寫(xiě)鎖,排它鎖,針對(duì)被鎖表,加鎖方可以讀寫(xiě),其他會(huì)話的寫(xiě)操作會(huì)直接失敗,讀操作會(huì)被阻塞掛起,解鎖以后,被掛起的線程會(huì)重新去爭(zhēng)搶資源。
加鎖指令:
lock tables 表名 write;
其它會(huì)話的讀操作、寫(xiě)操作在加鎖方釋放鎖之前都被阻塞掛起:


3.3.4.保護(hù)機(jī)制
讀鎖、寫(xiě)鎖中,加鎖方都只能讀當(dāng)前被自己鎖定的表,這是MySQL的一個(gè)保護(hù)機(jī)制,為的就是強(qiáng)制要求加鎖方給出一個(gè)說(shuō)法,到底準(zhǔn)備鎖多久,不給說(shuō)法不讓走。

3.4.行鎖
3.4.1.概述
innodb和myIsam最大的不同有兩點(diǎn),一是支持事務(wù),二是支持行級(jí)鎖。
3.4.2.什么是MVCC
行鎖沒(méi)有顯式的聲明辦法,而是藏在默認(rèn)實(shí)現(xiàn)中,MVCC 是 MySQL InnoDB 存儲(chǔ)引擎的默認(rèn)并發(fā)控制機(jī)制,其采用的就是表鎖。
并發(fā)控制有幾種處理方法,
第一種: 基于鎖的并發(fā)控制,程序員B開(kāi)始修改數(shù)據(jù)時(shí),給這些數(shù)據(jù)加上鎖,程序員A這時(shí)再讀,就發(fā)現(xiàn)讀取不了,處于等待情況,只能等B操作完才能讀數(shù)據(jù),這保證A不會(huì)讀到一個(gè)不一致的數(shù)據(jù),但是這個(gè)會(huì)影響程序的運(yùn)行效率。
第二種:MVCC,每個(gè)用戶連接數(shù)據(jù)庫(kù)時(shí),看到的都是某一特定時(shí)刻的數(shù)據(jù)庫(kù)快照,在B的事務(wù)沒(méi)有提交之前,A始終讀到的是某一特定時(shí)刻的數(shù)據(jù)庫(kù)快照,不會(huì)讀到B事務(wù)中的數(shù)據(jù)修改情況,直到B事務(wù)提交,才會(huì)讀取B的修改內(nèi)容。
MVCC其實(shí)就是實(shí)現(xiàn)事務(wù)的關(guān)鍵,后續(xù)會(huì)有文章專門(mén)深入聊事務(wù)的實(shí)現(xiàn),此處暫不展開(kāi)。
3.4.3.mvcc的使用
首先有一個(gè)需要糾正的是,很多地方都說(shuō)mvcc是通過(guò)手動(dòng)提交來(lái)觸發(fā)的,這是個(gè)誤導(dǎo),不管手動(dòng)提交還是自動(dòng)提交MVCC機(jī)制都是生效的,只是手動(dòng)提交用來(lái)觀察mvcc過(guò)程更加直觀。
此處為了直觀,我們也以手動(dòng)提交為例,首先通過(guò)set autocommit=0可以關(guān)閉自動(dòng)提交。關(guān)閉后每次執(zhí)行sql以后,通過(guò)commit命令來(lái)手動(dòng)提交,才會(huì)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生影響,否則只會(huì)對(duì)當(dāng)前操作方的數(shù)據(jù)快照有影響。innodb引擎中,其他客戶端想查看到最新的數(shù)據(jù)情況也必須通過(guò)commit指令來(lái)做一次同步(因?yàn)閕nnodb默認(rèn)隔離級(jí)別為可重復(fù)讀)。
當(dāng)一個(gè)會(huì)話修改某行數(shù)據(jù),未commit前,其他會(huì)話對(duì)該行數(shù)據(jù)的修改會(huì)阻塞掛起,直到先改那個(gè)會(huì)話commit為止。

3.4.4.間隙鎖
使用范圍條件匹配時(shí),innodb會(huì)給符合條件的已有數(shù)據(jù)記錄的索引加“范圍鎖”(范圍鎖是特殊的行鎖),對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做間隙(GAP),innodb也會(huì)對(duì)這個(gè)間隙加鎖,這種機(jī)制叫做“間隙鎖”。

3.4.5.行鎖變表鎖
任何需要全表掃描的情況時(shí),行鎖都會(huì)升級(jí)為表鎖。
因?yàn)镸ySQL不知道到底該鎖哪行,所以會(huì)將整個(gè)表都鎖起來(lái),然后再進(jìn)行全表掃描。
全表掃描的情況無(wú)非兩種:
- 沒(méi)建索引。
- 索引失效。

總結(jié)
到此這篇關(guān)于MySQL并發(fā)控制的文章就介紹到這了,更多相關(guān)MySQL并發(fā)控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
教你如何恢復(fù)使用MEB備份的MySQL數(shù)據(jù)庫(kù)
這篇文章主要介紹了教你如何恢復(fù)使用MEB備份的MySQL數(shù)據(jù)庫(kù)的具體方法,需要的朋友可以參考下2016-09-09
Mysql5.5升級(jí)到5.6的真實(shí)操作步驟示例
MySQL5.5升級(jí)到5.6涉及下載升級(jí)包、數(shù)據(jù)權(quán)限備份、配置文件備份、解壓安裝、數(shù)據(jù)導(dǎo)入、重啟服務(wù)、配置恢復(fù)等步驟2024-11-11
mysql隨機(jī)查詢?nèi)舾蓷l數(shù)據(jù)的方法
這篇文章主要介紹了mysql中獲取隨機(jī)內(nèi)容的方法,需要的朋友可以參考下2013-10-10
MySql 存儲(chǔ)引擎和索引相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了MySql 存儲(chǔ)引擎和索引相關(guān)知識(shí)總結(jié),文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06
union和子查詢中order?by一起使用導(dǎo)致排序失效問(wèn)題及解決
這篇文章主要介紹了union和子查詢中order?by一起使用導(dǎo)致排序失效問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
MySQL中常用查看鎖和事務(wù)的SQL語(yǔ)句詳解
這篇文章主要介紹了MySQL中常用查看鎖和事務(wù)的SQL語(yǔ)句,用于查看鎖和事務(wù)的狀態(tài),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07

