0. 對MySQL的鎖了解嗎
當數據庫有并發事務的時候,可能會產生數據的不一致,這時候需要一些機制來保證訪問的次序,鎖機制就是這樣的一個機制。
就像酒店的房間,如果大家隨意進出,就會出現多人搶奪同一個房間的情況,而在房間上裝上鎖,申請到鑰匙的人才可以入住并且將房間鎖起來,其他人只有等他使用完畢才可以再次使用。
鎖的作用:用于管理對共享資源的并發訪問,保證數據庫的完整性和一致性
1. 隔離級別與鎖的關系
在Read Uncommitted級別下,讀取數據不需要加共享鎖,這樣就不會跟被修改的數據上的排他鎖沖突
在Read Committed級別下,讀操作需要加共享鎖,但是在語句執行完以后釋放共享鎖;
在Repeatable Read級別下,讀操作需要加共享鎖,但是在事務提交之前并不釋放共享鎖,也就是必須等待事務執行完畢以后才釋放共享鎖。
SERIALIZABLE 是限制性最強的隔離級別,因為該級別鎖定整個范圍的鍵,并一直持有鎖,直到事務完成。
2. MySQL三種鎖的級別。按照鎖的粒度分數據庫鎖有哪些?鎖機制與InnoDB鎖算法
在關系型數據庫中,可以按照鎖的粒度把數據庫鎖分為行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )。
MyISAM和InnoDB存儲引擎使用的鎖:
MyISAM采用表級鎖(table-level locking)。
InnoDB支持行級鎖(row-level locking)和表級鎖,默認為行級鎖
表級鎖
表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MYISAM與INNODB都支持表級鎖定。表級鎖定分為表共享讀鎖(共享鎖)與表獨占寫鎖(排他鎖)。
-
特點
開銷小,加鎖快;
不會出現死鎖;
鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
行級鎖
行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。
行級鎖能大大減少數據庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為 共享鎖 和 排他鎖。
-
特點
開銷大,加鎖慢;
會出現死鎖;
鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
頁面鎖
頁級鎖是MySQL中鎖定粒度介于行級鎖和表級鎖中間的一種鎖。
表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。
-
特點
開銷和加鎖時間界于表鎖和行鎖之間;
會出現死鎖;
鎖定粒度界于表鎖和行鎖之間,并發度一般
封鎖粒度小:
好處:鎖定的數據量越少,發生鎖爭用的可能就越小,系統的并發程度就越高;
壞處:系統開銷大(加鎖、釋放鎖、檢查鎖的狀態都需要消耗資源)
3. MySQL常見的鎖類型?
排它鎖(Exclusive Lock)/ X鎖
排他鎖又叫做寫鎖。 當用戶要進行數據的寫入時,對數據加上排他鎖。排他鎖只可以加一個,他和其他的排他鎖,共享鎖都相斥。
事務對數據加上X鎖時,只允許此事務讀取和修改此數據,并且其它事務不能對該數據加任何鎖;
SELECT * FROM table_name WHERE ... FOR UPDATE; # 排它鎖
共享鎖(Shared Lock)/ S鎖
共享鎖又叫做讀鎖。 當用戶要進行數據的讀取時,對數據加上共享鎖。共享鎖可以同時加上多個。
加了S鎖后,該事務只能對數據進行讀取而不能修改,并且其它事務只能加S鎖,不能加X鎖
場景:相當于對于同一把門,它擁有多個鑰匙一樣。
在執行語句后面加上lock in share mode就代表對某些資源加上共享鎖了。
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE; # 共享鎖
意向鎖(Intention Locks)
意向鎖都是表鎖。意向鎖的存在是為了允許事務在行級上的鎖和表級上的鎖同時存在。
意向共享鎖(IS Lock)事務想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖。
意向排他鎖(IX Lock)事務想要在獲得表中某些記錄的互斥鎖,需要在表上先加意向互斥鎖。
由于InnoDB存儲引擎支持的是行級別的鎖,因此意向鎖(因為意向鎖是表鎖)其實不會阻塞除全表掃以外的任何請求。故表級意向鎖與行級鎖的兼容性如下所示
是否兼容當前鎖模式 | X | IX | S | IS |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 兼容 | 沖突 | 兼容 |
S | 沖突 | 沖突 | 兼容 | 兼容 |
IS | 沖突 | 兼容 | 兼容 | 兼容 |
IS/IX 鎖之間都是兼容的;
好處:如果一個事務想要對整個表加X鎖,就需要先檢測是否有其它事務對該表或者該表中的某一行加了鎖,這種檢測非常耗時。有了意向鎖之后,只需要檢測整個表是否存在IX/IS/X/S鎖就行了
意向鎖到底有什么作用
innodb的意向鎖主要用于解決多粒度的鎖并存的情況。
比如事務A要在一個表上加S鎖,如果表中的一行已被事務B加了X鎖,那么該鎖的申請也應被阻塞。
如果表中的數據很多,逐行檢查鎖標志的開銷將很大,系統的性能將會受到影響。
為了解決這個問題,可以在表級上引入新的鎖類型來表示其所屬行的加鎖情況,這就引出了“意向鎖”的概念。
舉個例子,如果表中記錄1億,事務A把其中有幾條記錄上了行鎖了,這時事務B需要給這個表加表級鎖,如果沒有意向鎖的話,那就要去表中查找這一億條記錄是否上鎖了。如果存在意向鎖,那么假如事務A在更新一條記錄之前,先加意向鎖,再加X鎖,事務B先檢查該表上是否存在意向鎖,存在的意向鎖是否與自己準備加的鎖沖突,如果有沖突,則等待直到事務A釋放,而無須逐條記錄去檢測。事務B更新表時,其實無須知道到底哪一行被鎖了,它只要知道反正有一行被鎖了就行了。
主要作用是處理行鎖和表鎖之間的矛盾,能夠顯示“某個事務正在某一行上持有了鎖,或者準備去持有鎖”
4. 知道mysql中的鎖嗎,說一下表鎖,行鎖,如何上鎖(for update),舉個例子在什么時候事務會進入阻塞狀態
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
因為不同鎖之間的兼容性關系,在有些時刻一個事務中的鎖需要等待另一個事務中的鎖釋放它占有的資源,這就是阻塞。
5. mysql共享鎖與排他鎖
共享鎖與排他鎖是行級鎖
共享鎖又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對于同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。
排他鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他所并存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改。
mysql InnoDB引擎默認的修改數據語句,update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型,如果加排他鎖可以使用select ...for update語句,加共享鎖可以使用select ... lock in share mode語句。所以加過排他鎖的數據行在其他事務種是不能修改數據的,也不能通過for update和lock in share mode鎖的方式查詢數據,但可以直接通過select ...from...查詢數據,因為普通查詢沒有任何鎖機制。
6. 行鎖的三種算法,以及解決的問題
InnoDB行鎖時通過給索引上的索引項加鎖來實現的,Oracle時通過在數據塊中相對應數據行加鎖來實現。
InnoDB這種行鎖實現特點意味著,只有通過索引條件檢索條件數據,InnoDB才使用行鎖,否則InnoDB將使用表鎖。
行鎖的三種算法:
Record鎖:單個記錄上的鎖??
gap鎖:間隙鎖,鎖定一個范圍,但不包含記錄本身
next-key鎖:Gap Lock + Record Lock,鎖定一個范圍,并且鎖定記錄本身
記錄鎖 Record Locks
單個記錄上的鎖
Record Lock是對索引項加鎖。記錄鎖有兩種模式,S模式和X模式。
記錄鎖總會鎖住索引記錄,鎖住的是key。
即使表沒有建立索引,InnoDB也會創建一個隱藏的聚簇索引(隱藏的遞增主鍵索引),并使用此索引進行記錄鎖定。
場景:一個索引有10,11,13,20這4個值, Innodb可以使用記錄鎖 Record Locks 將10,11,13,20這四個索引鎖住
間隙鎖 Gap Locks
間隙鎖,鎖定一個范圍,但不包含記錄本身
間隙鎖作用在索引記錄之間的間隔,又或者作用在第一個索引之前,最后一個索引之后的間隙。不包括索引本身。
例如
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
這條語句阻止其他事務插入10和20之間的數字,無論這個數字是否存在。 間隙可以跨越0個,單個或多個索引值。
InnoDB中的間隙鎖的唯一目的是防止其他事務插入間隙。
間隙鎖時針對事務隔離級別為可重復讀或以上級別而配的。如果事務隔離級別改為RC,則間隙鎖會被禁用。
Gap Lock在InnoDB的唯一作用就是防止其他事務的插入操作,以此防止幻讀
場景:一個索引有10,11,13,20這4個值, Innodb可以使用間隙鎖 Gap Lock 將(-∞,10),(10,11),(11,13),(13,20),(20, +∞)這五個范圍鎖住??
Next-key鎖
Gap Lock+Record Lock,鎖定一個范圍,并且鎖定記錄本身
Next-key鎖是在下一個索引記錄本身和索引之前的gap加上S鎖或是X鎖(如果是讀就加上S鎖(共享鎖),如果是寫就加X鎖(排他鎖))。
InnoDB使用next-key鎖對索引進行掃描和搜索,這樣就讀取不到幻象行,避免了幻讀的發生。
-
當查詢的索引含有唯一屬性時,InnoDB存儲引擎會對Next-Key Lock 進行優化,將其降級為 Record Lock,即僅鎖住索引本身,而不是范圍。
當查詢的索引為輔助索引時,默認使用Next-Key Locking技術進行加鎖,鎖定范圍是前一個索引到后一個索引之間范圍。
場景:一個索引有10,11,13,20這4個值, 那么Next-key鎖 鎖住的范圍 (-∞,10],(10,11],(11,13],(13,20],(20, +∞),這五個范圍是左閉右開的,可以看出他不僅鎖住了一個范圍,也會鎖定記錄本身。
InnoDB這種行鎖的實現特點意味著,如果不通過索引條件檢索數據,那么InnoDB將對表中的所有記錄加鎖,實際效果跟鎖表一樣。
采用Next-Key Lock的鎖定技術稱為Next-Key Locking。這種設計的目的是為了解決幻讀(Phantom Problem)。利用這種鎖定技術,鎖定的不是單個值,而是一個范圍。
7. 實現一個讀寫鎖
https://blog.csdn.net/u014316026/article/details/78726459
讀鎖(共享鎖)是讀取操作創建的鎖。其他用戶可以并發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放所有共享鎖。 如果事務T對數據A加上共享鎖后,則其他事務只能對A再加共享鎖,不能加排他鎖。獲準共享鎖的事務只能讀數據,不能修改數據。
寫鎖(排他鎖)如果事務T對數據A加上排他鎖后,則其他事務不能再對A加任任何類型的封鎖。獲準排他鎖的事務既能讀數據,又能修改數據。
-
事務可以通過以下語句給sql加共享鎖和排他鎖:
共享鎖:select …… lock in share mode;
排他鎖:select …… for update;
共享鎖:
排他鎖:
8. 什么是三級封鎖協議?
一級封鎖協議
修改數據加x鎖直到事務結束才釋放。可以解決丟失修改問題(兩個事務不能同時對一個數據加X鎖,避免了修改被覆蓋);
在一級封鎖協議中,讀數據是不需要加鎖的,它修改數據加x鎖直到事務結束才釋放。。
二級封鎖協議
在一級的基礎上,事務在讀取數據之前必須先加S鎖,讀完后釋放S鎖。可以解決臟讀問題(如果已經有事務在修改數據,就意味著已經加了X鎖,此時想要讀取數據的事務并不能加S鎖,也就無法進行讀取,避免了讀取臟數據);
二級封鎖協議除防止了丟失修改,還可以進一步防止讀“臟”數據。但在二級封鎖協議中,由于讀完數據后即可釋放S鎖,所以它不能保證可重復讀。
三級封鎖協議
在二級的基礎上,事務在讀取數據之前必須先加S鎖,直到事務結束才能釋放。
可以解決不可重復讀問題(避免了在事務結束前其它事務對數據加X鎖進行修改,保證了事務期間數據不會被其它事務更新)
上述三級協議的主要區別在于什么操作需要申請封鎖,以及何時釋放。
9. 什么是兩段鎖協議?
https://www.cnblogs.com/mysql-hang/articles/11027685.html
事務必須嚴格分為兩個階段對數據進行加鎖和解鎖的操作,第一階段加鎖,第二階段解鎖。也就是說一個事務中一旦釋放了鎖,就不能再申請新鎖了。
目的:引入2PL是為了保證事務的隔離性,保證并發調度的準確性,多個事務在并發的情況下依然是串行的。
可串行化調度是指,通過并發控制,使得并發執行的事務結果與某個串行執行的事務結果相同。事務遵循兩段鎖協議是保證可串行化調度的充分條件
傳統RDBMS加鎖的一個原則,就是2PL (二階段鎖):Two-Phase Locking。相對而言,2PL比較容易理解,說的是鎖操作分為兩個階段:加鎖階段與解鎖階段,并且保證加鎖階段與解鎖階段不相交。下面,仍舊以MySQL為例,來簡單看看2PL在MySQL中的實現。
從上圖可以看出,2PL就是將加鎖/解鎖分為兩個完全不相交的階段。加鎖階段:只加鎖,不放鎖。解鎖階段:只放鎖,不加鎖。
2PL主要用于Mysql(僅限innodb)中使用的。保證事務的一致性與隔離性。
10. 什么是樂觀鎖和悲觀鎖?
樂觀鎖
樂觀鎖不是數據庫自帶的,需要我們自己去實現。樂觀鎖是指操作數據庫時(更新操作),想法很樂觀,認為這次的操作不會導致沖突,在操作數據時,并不進行任何其他的特殊處理(也就是不加鎖),而在進行更新后,再去判斷是否有沖突了。
樂觀鎖:總是假設最好的情況,每次去拿數據的時候都認為別人不會修改(天真),操作數據時不會上鎖,但是更新時會判斷在此期間有沒有別的事務更新這個數據,若被更新過,則失敗重試;適用于讀多寫少的場景。
樂觀鎖的實現方式有:
-
版本號機制:加一個版本號或者時間戳字段,每次數據更新時同時更新這個字段;
一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
-
場景:
假設數據庫中帳戶信息表中有一個 version 字段,當前值為 1 ;而當前帳戶余額字段( balance )為 $100 。
操作員 A 此時將其讀出( version=1 ),并從其帳戶余額中扣除
100-$50 )。
在操作員 A 操作的過程中,操作員B 也讀入此用戶信息( version=1 ),并從其帳戶余額中扣除
100-$20 )。
操作員 A 完成了修改工作,將數據版本號加一( version=2 ),連同帳戶扣除后余額( balance=$50 ),提交至數據庫更新,此時由于提交數據版本大于數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新為 2 。
操作員 B 完成了操作,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操作員 B 提交的數據版本號為 2 ,數據庫記錄當前版本也為 2 ,不滿足 “ 提交版本必須大于記錄當前版本才能執行更新 “ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。
這樣,就避免了操作員 B 用基于 version=1 的舊數據修改的結果覆蓋操作員A 的操作結果的可能。
使用實例:
1. SELECT data AS old_data, version AS old_version FROM …;
2. 根據獲取的數據進行業務操作,得到new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 樂觀鎖獲取成功,操作完成
} else {
// 樂觀鎖獲取失敗,回滾并重試
}
-
CAS算法:compare and swap(比較與交換),是一種有名的無鎖算法。先讀取想要更新的字段或者所有字段,更新的時候比較一下,只有字段沒有變化才進行更新。一般情況下是一個自旋操作,即不斷的重試
無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
-
CAS算法涉及到三個操作數
需要讀寫的內存值(內存位置) V
進行比較的值(原值) A
擬寫入的新值(新值) B
當且僅當 內存位置V 的值等于 原值A 時,CAS通過原子方式用 新值B 來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。
CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”
樂觀鎖不是鎖,樂觀鎖是一種思想,版本號機制和CAS算法是實現這種思想的一種實現方式
樂觀鎖的優點:從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷,大大提升了大并發量下的系統整體性能表現。
-
樂觀鎖的缺點:
-
ABA問題:
- 如果一個變量V初次讀取的時候是A值,并且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因為在這段時間它的值可能被改為其他值,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個問題被稱為CAS操作的 "ABA"問題。
-
循環時間長開銷大
- 自旋CAS(也就是不成功就一直循環執行直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。
-
總結:讀用樂觀鎖,寫用悲觀鎖。
悲觀鎖
悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)
認為數據隨時會被修改(被害妄想癥),因此每次讀取數據之前都會上鎖,防止其它事務讀取或修改數據(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程);
應用于數據更新比較頻繁的場景;
傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
悲觀鎖的實現:首先實現悲觀鎖時,我們必須先使用
set autocommit=0;
關閉mysql的autoCommit屬性。因為我們查詢出數據之后就要將該數據鎖定。
關閉自動提交后,我們需要手動開啟事務。
// 1.開始事務
begin; 或者 start transaction;
// 2.查詢出商品信息,然后通過for update鎖定數據防止其他事務修改
select status from t_goods where id=1 for update;
// 3.根據商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
// 4.修改商品status為2
update t_goods set status=2;
// 5.提交事務
commit; --執行完畢,提交事務
上述就實現了悲觀鎖,悲觀鎖就是悲觀主義者,它會認為我們在事務A中操作數據1的時候,一定會有事務B來修改數據1,所以,在第2步我們將數據查詢出來后直接加上排它鎖(X)鎖,防止別的事務來修改事務1,直到我們commit后,才釋放了排它鎖。
優點:保證了數據處理時的安全性。
缺點:加鎖造成了開銷增加,并且增加了死鎖的機會。降低了并發性。
樂觀鎖更新有可能會失敗,甚至是更新幾次都失敗,這是有風險的。所以如果寫入居多,對吞吐要求不高,可使用悲觀鎖。
11. 什么是死鎖?
死鎖是指兩個或兩個以上事務在執行過程中因爭搶鎖資源而造成的互相等待(形成環路)的現象
表級鎖不會產生死鎖。所以解決死鎖主要還是針對于最常用的InnoDB。
死鎖的關鍵在于:兩個(或以上)的Session加鎖的順序不一致。那么對應的解決死鎖問題的關鍵就是:讓不同的session加鎖有次序。
12. 死鎖怎么解決?
查出的線程殺死 kill SELECT trx_MySQL_thread_id FROM information_schema.INNODB_TRX;
設置鎖的超時時間:Innodb 行鎖的等待時間,單位秒。可在會話級別設置,RDS 實例該參數的默認值為 50(秒)。生產環境不推薦使用過大的 innodb_lock_wait_timeout參數值該參數支持在會話級別修改,方便應用在會話級別單獨設置某些特殊操作的行鎖等待超時時間,如下:set innodb_lock_wait_timeout=1000; —設置當前會話 Innodb 行鎖等待超時時間,單位秒。
如果不同程序會并發存取多個表,盡量約定以相同的順序訪問表,可以大大降低死鎖機會。
在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖產生概率;
對于非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率;
13. 鎖的優化策略
- 讀寫分離
- 分段加鎖
- 減少鎖持有的時間
- 多個線程盡量以相同的順序去獲取資源