對酒當歌,人生幾何! 朝朝暮暮,惟有己脫。 html
苦苦尋覓找工做之間,卻不知今日之時乃我心之痛,難到是我不配擁有工做嘛。自面試後他所謂的等待都過去一段時日,惋惜在下京東上的小金庫都要見低啦。往往想到不禁心中一緊。正處爲難之間,手機突然來了個短信預定後續面試。 我即刻三下五除二拎包踢門而出。飛奔而去。 面試
此刻面試門外首先映入眼簾的是一個白色似皮球的東西,似圓非圓。好奇冬瓜落地通常。上半段還有一段溼溼的部分,顯得尤其入目。這是什麼狀況?算法
緊接着現身一名中年男子。他身着純白色T桖衫的,一灰色寬鬆的休閒西褲,腰圍至少得三十好幾。外加一雙夏日必備皮製涼鞋。只見,他正低頭看着手上的一張A4紙。透過一頭黑色短髮。滿臉的贅肉橫生。外加上那大腹便便快要把那T桖衫給撐爆的肚子。數據庫
看得我好生懼怕,不禁得嚥了咽口水,生怕本身說錯話。這宛如一顆肉糉呀。不在職場摸滾打拼八、9年,也不會有當前這景象。 服務器
什麼是鎖
面試官:: 你是來參加面試的吧? 吒吒輝: 不 不 不,我是來參加複試呢。微信
面試官:: 看到上次別人點評,MySQL優化還闊以。那你先談談對鎖的理解?多線程
吒吒輝: 嘿嘿,還好!併發
鎖是計算機在進行多 進程、線程執行調度時強行限制資源訪問的同步機制,用於在併發訪問時保證數據的一致性、有效性;性能
鎖是在執行多線程時,用於強行限制資源訪問的同步機制,即用在併發控制中保證對互斥的要求。學習
通常的鎖是建議鎖(advisory lock),每一個線程在訪問對應資源前都需獲取鎖的信息,再根據信息決定是否能夠訪問。若訪問對應信息,鎖的狀態會改變爲鎖定,所以其它線程此時不會來訪問該資源,當資源結束後,會恢復鎖的狀態,容許其餘線程的訪問。
有些系統有強制鎖(mandatory lock),如有未受權的線程想要訪問鎖定的數據,在訪問時就會產生異常。 ---《維基百科》
鎖的類型和應用原理
面試官:: 那通常數據庫有哪些鎖? 通常怎麼使用?
此刻,用我那呆若木雞的眼神看向面試官,心裏實屬尷尬+懼怕,數據庫不就是共享和互斥鎖嗎?
這樣看來,是我太嫩。此處必有坑。卻不知此刻我心裏已把你拿捏,定斬不饒。
吒吒輝: 數據庫的鎖根據不一樣劃分方式有不少種說法,在業務訪問上有如下兩種:
- 排他鎖
在訪問共享資源以前對其進行加鎖,在訪問完成後進行解鎖操做。 加鎖成功後,任何其它線程請求來獲取鎖都會被阻塞,直到當前線自行釋放鎖。
線程3狀態:就緒、阻塞、執行
如解鎖時,有一個以上的線程阻塞(資源已釋放),那麼全部嘗試獲取該鎖的線程都被CPU認爲就緒狀態, 若是第一個就緒狀態的線程又執行加鎖操做,那麼其餘的線程又會進入就緒狀態。 在這種方式下,只能有一個線程訪問被互斥鎖保護的資源
故此,MySQL的SQL語句加了互斥鎖後,只有接受到請求並獲取鎖的線程纔可以訪問和修改數據。 由於互斥鎖是針對線程訪問控制而不是請求自己。
- 共享鎖
被加鎖資源是可被共享的,但僅限於讀請求。它的寫請求只能被獲取到鎖的請求獨佔。 也就是加了共享鎖的數據,只可以當前線程修改,其它線程只能讀數據,並不能修改。
吒吒輝: 在 SQL 請求上可分爲讀、寫鎖。但本質仍是對應對共享鎖和排它鎖。
面試官: 那 SQL 請求上不加鎖怎麼訪問? 爲啥說它們屬於共享鎖和排他鎖? 這之間有何聯繫?
吒吒輝: 除加鎖讀外,還有一種不加鎖讀的狀況。這種方式稱爲快照讀,讀請求加鎖稱爲共享讀。
針對請求加共享、排它鎖的緣由在於,讀請求天生是冪等性的,不論你讀多少次數據不會發生變化,因此給讀請求加上鎖就應該爲共享鎖。 否則怎麼保證它的特色呢? 而寫請求,自己就需對數據進行修改,因此就須要排它鎖來保證數據修改的一致性。
吒吒輝: 若是按照鎖的顆粒度劃分看,就有表鎖和行鎖
- 表鎖: 是MySQL中最基本的鎖策略,而且是開銷最小的策略。併發處理較少。表鎖由MySQL服務或存儲引擎管理。多數狀況由服務層管理,具體看SQL操做。
例如:服務器會爲諸如 ALTER TABLE 之類的語句使用表鎖 ,而忽略存儲引擎的鎖。
加鎖機制:
它會鎖定整張表。一個用戶在對錶進行寫操做(插人、刪除、更新等)前,須要先得到寫鎖,這會阻塞其餘用戶對該表的全部讀寫操做。只有沒有寫鎖時,其餘用戶才能獲取到讀鎖。
-
行鎖:
鎖定當前訪問行的數據,併發處理能力很強。但鎖開銷最大。具體視行數據多少決定。由innoDB存儲引擎支持。 -
頁級鎖: 頁級鎖是 MySQL 中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以,採起了折衷的頁級鎖,一次鎖定相鄰的一組記錄。由BDB 存儲引擎管理頁級鎖。
面試官: 爲啥是表鎖開銷小,而不是行鎖呢? 畢竟表鎖鎖定是整張表
吒吒輝: 表鎖鎖定的是表沒錯,但它不是把表裏面全部的數據行都上鎖,至關因而封鎖了表的入口,這樣它只是須要判斷每一個請求是否能夠獲取到表的鎖,沒有就不鎖定。
而行鎖是針對表的每一行數據,數據量一多,鎖定內容就多,故開銷大。 但因它顆粒度小,鎖定行不會影響到別的行。因此併發就高。而若是表鎖在一個入口就卡死了,那總體請求處理確定就會降低。
面試官: 我記得行鎖裏面有幾種不一樣的實現方式,你知道嗎?
您可真貼心啊,替我考慮這麼多,大佬都是這麼心比針細? 我要是說不知道,你總是不是又準備給出穿小鞋啦。強忍心裏啃人的衝動
ps:讀懂圖,說明你有故事
吒吒輝: innodb雖支持行鎖,但鎖實現的算法卻和SQL的查詢形式有關係:
-
Record Lock(記錄鎖):
單個行記錄上的鎖。也就是咱們平常認爲的行鎖。由where =
的形式觸發 -
Gap Lock(間隙鎖
):間隙鎖,鎖定一個範圍,但不包括記錄自己(它鎖住了某個範圍內的多個行,包括根本不存在的數據)。
GAP鎖的目的,是爲了防止事務插入而致使幻讀的狀況。該鎖只會在隔離級別是RR或者以上的級別內存在。間隙鎖的目的是爲了讓其餘事務沒法在間隙中新增數據。 SQL裏面用 where >、>=等範圍條件觸發,
但會根據鎖定的範圍內,是否包含了表中真實存在的記錄進行變化,若是存在真實記錄就會進化爲 臨建鎖
。反之就爲間隙所。
-
Next-Key Lock(臨鍵鎖)
:它是記錄鎖和間隙鎖的結合,鎖定一個範圍,而且鎖定記錄自己。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題。next-key 鎖是InnoDB默認的。是一個左開右閉的規則 -
IS鎖:意向共享鎖
、Intention Shared Lock。當事務準備在某條記錄上加S(讀)鎖時,須要先在表級別加一個IS鎖。 -
IX鎖:意向排它鎖
、Intention Exclusive Lock。當事務準備在某條記錄上加X(寫)鎖時,須要先在表級別加一個IX鎖。
面試官: 那這個東西是怎麼實現的?
t(id PK, name KEY, sex, flag);
表中有四條記錄:
1, zhazhahui, m, A 3, nezha, m, A 5, lisi, m, A 9, wangwu, f, B
-
記錄鎖
select * from t where id=1 for update;
鎖定 id =1的記錄 -
間隙鎖
select * from t where id > 3 and id < 9 ;
鎖定(3,5],(5,9)範圍的值,由於當前訪問3到9的範圍記錄,就須要鎖定表裏面已經存在的數據來解決幻讀和不可重複讀的問題
- 臨建鎖
select * from t where id >=9 ;
會鎖定 [9,+∞) 。查詢會先選中 9 號記錄,因此鎖定範圍就以9開始到正無窮數據。
面試官: 那意向排它、共享鎖呢?是怎麼個內容
吒吒輝: 意向排它鎖和意向共享鎖,是針對當前SQL請求訪問數據行時,會提早進行申請訪問,若是最終行鎖未命中就會退化爲該類型
的表鎖。
面試官: 那有這個意向排它鎖有什麼好處呢?
吒吒輝: 可提早作預判,每次嘗試獲取行鎖以前會檢查是否有表鎖,若是存在就不會繼續申請行鎖,從而減小鎖的開銷。從而整個表就退化爲表鎖。
面試官: 那你動手給我演示下每一個場景
嗯。。。(瞳孔放大2倍)我這不說的很明白嗎?
難道故意和做對,這是幹嗎啊。欺負人嘛不是
只見那面試官突然翹起來二郎腿,還有節拍的抖動着腿,看向我。一看就是抖音整多了 哎,沒辦法 官大以及壓死人。打碎了牙齒本身咽。你給我看細細看好了,最好眼睛都別眨
吒吒輝: 由於鎖就是解決事務併發的問題,因此記錄鎖就不演示了,直接遊蕩在間隙和臨建鎖裏面。
創建語句:
CREATE TABLE `t1` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, `age` tinyint(3) unsigned DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
表數據:
間隙鎖:
- 關閉 MySQL 默認的事務自動提交機制。
-
關閉前:
-
關閉後:
-
加鎖:
直接插入 >8 的數據就阻塞,都會上鎖。爲的就解決插入新數據而致使幻讀。
【啊!幻讀不知道呀。下篇文章給你們安排上】
面試官: 你這條件不是>=8嗎? 那等於8呢? 被吃辣?
吒吒輝: 彆着急嘛,這不還沒說完嗎。爲何不指定8呢?
由於 >=8 的條件會從間隙鎖升級爲臨建鎖,由於你條件裏面包含了 8 這個真實存在的數據。因此會把它鎖起來。以下:
因此,最終的行鎖會和SQL語句的條件觸發有關係,一旦範圍查詢包含了數據庫裏面真實存在數據,就會升級爲臨建鎖。不要問我爲何? 看前面的定義
面試官獨白:這小夥多少看來還有有點貨,不錯。此刻面試官露出一絲笑容。卻不知他心裏又開醞釀起了新的想法。就等我入甕
面試官: 那什麼場景下行鎖不會生效呢?鎖 鎖定的又是什麼?
此刻,我呆了,這都什麼跟什麼啊。不帶這麼玩的吧。天殺的,淨使壞
鎖的觸發機制
吒吒輝: innodb的行鎖是根據索引觸發,若是沒有相關的索引,那行鎖將會退化成表鎖(即鎖定整個表裏的行)。 而 鎖 鎖定的是索引即索引樹裏面的數據庫字段的值。
- id爲主鍵索引字段。
- 給 age 字段上鎖
- age 字段沒索引,退化成表鎖。直接查詢將失敗。
有索引,用索引字段查詢可得數據,其他字段查詢將失敗。由於獲取不到行鎖,只能等待。而鎖定的是索引,故此其它用其它索引值查詢能拿查詢數據
- 索引字段上鎖
- 索引當前字段鎖定,用其他索引字段可查詢
- 不是索引字段都差不到。
面試官: 你前面說到的鎖能夠解決事務併發,然而MVCC也是用於解決併發,那幹嗎還用鎖來呢?你給說說
吒吒輝: 經過MVCC能夠解決髒讀、不可重複讀、幻讀這些讀一致性問題,但實際上這只是解決了普通select語句的數據讀取問題。
事務利用MVCC進行的讀取操做稱之爲快照讀,全部普通的SELECT語句在READ COMMITTED、REPEATABLE READ隔離級別下都算是快照讀。
除了快照讀以外,還有一種是鎖定讀,即在讀取的時候給記錄加鎖,在鎖定讀的狀況下依然要解決髒讀、不可重複讀、幻讀的問題。
好比:若是 1 4 7 9 的數據。若是條件爲 where > 4 的,那若是不鎖定到 (4,7] (7,9],(9,+∞)。那勢必就會早幻讀,不可重複讀的問題。
ps:不重複讀?髒讀是如何產生的?
死鎖
面試官: 那你說下數據庫的死鎖是個什麼狀況?
吒吒輝: 死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而致使惡性循環。
當事務試圖以不一樣的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖。
通常可經過死鎖檢測和死鎖超時機制來解決該問題。
死鎖檢查:
像InnoDB存儲引擎,就能檢測到死鎖的循環依賴,並當即返回一個錯誤。不然死鎖會致使出現很是慢的查詢。經過參數 innodb_deadlock_detect 設置爲on,來開啓。
超時機制:
就是當查詢的時間達到鎖等待超時的設定後放棄鎖請求。InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾(這是相對比較簡單的死鎖回滾算法)。
可經過配置參數 innodb_lock_wait_timeout 用來設置超時時間。若是有些用戶使用哪一種大事務,就設置鎖超時時間大於事務執行時間。
但這種狀況下死鎖超時檢查的發現時間是沒法接受的。
面試官: 那你說說InnoDB和MyisAM是如何發現死鎖的?
吒吒輝:
- innodb
數據庫會把事務單元鎖維持的鎖和它所等待的鎖都記錄下來,Innodb提供了wait-for graph算法來主動進行死鎖檢測,每當加鎖請求沒法當即知足須要進入等待時,wait-for graph算法都會被觸發。當數據庫檢測到兩個事務不一樣方向地給同一個資源加鎖(產生循序),它就認爲發生了死鎖,觸發wait-for graph算法。
好比:事務1給A加鎖,事務2給B加鎖,同時事務1給B加鎖(等待),事務2給A加鎖就發生了死鎖。那麼死鎖解決辦法就是終止一邊事務的執行便可,這種效率通常來講是最高的,也是主流數據庫採用的辦法。
Innodb目前處理死鎖的方法就是將持有最少行級排他鎖的事務進行回滾
。這是相對比較簡單的死鎖回滾方式。死鎖發生之後,只有部分或者徹底回滾其中一個事務,才能打破死鎖。
對於事務型的系統,這是沒法避免的,因此應用程序在設計必須考慮如何處理死鎖。大多數狀況下只須要從新執行因死鎖回滾的事務便可。
- MyisAM
MyisAM自身只支持表級鎖,故加鎖後一次性獲取的。因此資源上不會出現多個事務之間互相須要對方釋放鎖以後再來進行處理。故不會有死鎖
面試官: wait-for graph 算法怎麼理解?
吒吒輝: 以下所示,四輛車就是死鎖
它們相互等待對方的資源,並且造成環路!每輛車可看爲一個節點,當節點1須要等待節點2的資源時,就生成一條有向邊指向節點2,最後造成一個有向圖。咱們只要檢測這個有向圖是否出現環路便可,出現環路就是死鎖!這就是wait-for graph算法。
Innodb將各個事務看爲一個個節點,資源就是各個事務佔用的鎖,當事務1須要等待事務2的鎖時,就生成一條有向邊從1指向2,最後行成一個有向圖。
面試官: 既然死鎖沒法避免,那如何減小發生呢?
吒吒輝:
-
對應用程序進行調整/修改。某些狀況下,你能夠經過把大事務分解成多個小事務,使得鎖可以更快被釋放,從而極大程度地下降死鎖發生的頻率。在其餘狀況下,死鎖的發生是由於兩個事務採用不一樣的順序操做了一個或多個表的相同的數據集。須要改爲以相同順序讀寫這些數據集,換言之,就是對這些數據集的訪問採用串行化方式。這樣在併發事務時,就讓死鎖變成了鎖等待。
-
修改表的schema,例如:刪除外鍵約束來分離兩張表,或者添加索引來減小掃描和鎖定的行。
-
若是發生了間隙鎖,你能夠把會話或者事務的事務隔離級別更改成RC(read committed)級別來避免,能夠避免掉不少由於gap鎖形成的死鎖,但此時須要把binlog_format設置成row或者mixed格式。
-
爲表添加合理的索引,不走索引將會爲表的每一行記錄添加上鎖(等同表鎖),死鎖的機率大大增大。
-
爲了在單個InnoDB 表上執行多個併發寫入操做時避免死鎖,能夠在事務開始時經過爲預期要修改的每一個元祖(行)使用SELECT ... FOR UPDATE語句來獲取必要的鎖,即便這些行的更改語句是在以後才執行的。
-
經過SELECT ... LOCK IN SHARE MODE獲取行的讀鎖後,若是當前事務再須要對該記錄進行更新操做,則頗有可能形成死鎖。因進行獲鎖讀取在修改
這時,只見對面所坐面試官,捋了捋那沒有毛髮的下巴,故做深思熟慮,像是在端詳這什麼。 難道 難道 是讓我經過了嗎?
此刻心裏猶如小鹿亂撞,吶喊到我要幹它二量。真的是不容易。 就在此時,他起身而立,那白色T桖衫包裹着那甩大肚子,猶如波浪上下翻滾。一看就是沒少在酒桌上擼肉。
只見開口到,小夥子不錯啊。
這是確定我嗎? 不容易啊,今天不開幾把LOL,難消我心頭之恨
面試官: 其實這數據庫嘛 ,內容仍是有不少的,你回去準備下,下一次的面試吧
。。。。什麼個玩意兒,下次? 那就是此次不行啦, 這還沒考夠啊,下巴原本沒毛,你捋個什麼勁兒,整得個神神忽忽的。 此時心裏猶如翻江倒海,猛龍過江。白鶴亮翅的衝動打他,奈何我這小身板子不行
吒吒輝: 那行吧,下次是多久啊,我這好多天都沒整頓好的啦,你給我個準信唄。
我用那水汪汪可憐的小眼神望向他說到。他卻很斯文的笑着,說道
面試官: 快了,小夥子彆着急,我看好你的,加油
我加你那擼啊絲壓榨花生油。 面個試,還嫌我臉上出油出的很少,都是被你擠出來的。只有強忍住心裏的衝動。 哎 官大一級壓死人啊 吒吒輝: 行吧,那我走啦 此刻,露出我那灰溜溜的背影,猶如魯迅先生筆下的孔乙己
參考: 《高性能MySQL》 https://zhuanlan.zhihu.com/p/29150809 https://www.cnblogs.com/yulibostu/articles/9978618.html
若有幫助,歡迎點贊關注分享額,微信搜索【蓮花童子哪吒】 獲取體系化內容,加我歸入羣聊,一塊兒交流學習進步提高。
最近吒吒輝創了技術交流羣,主題就是 【知識盛宴】,你們一塊兒每週攻克一個難題,充分進行自我能力迭代提高,不單純技術額!!!
有興趣的讀者,能夠掃一掃吒吒輝微信二維碼,備註 「加羣」 便可。據說裏面的人說話又好聽,各個都是人才。
若是你們在閱讀過程當中,發現了不理解或有錯誤的地方,歡迎跟在底部留言,大家的每一條留言,吒吒輝都會回覆。