什么是鎖?MySQL 中提供了幾類鎖?
鎖是實(shí)現(xiàn)數(shù)據(jù)庫并發(fā)控制的重要手段,可以保證數(shù)據(jù)庫在多人同時(shí)操作時(shí)能夠正常運(yùn)行。MySQL 提供了全局鎖、行級(jí)鎖、表級(jí)鎖。其中 InnoDB
支持表級(jí)鎖和行級(jí)鎖,MyISAM 只支持表級(jí)鎖。
什么是死鎖?
是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的過程稱為死鎖。
死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的過程稱為死鎖。
常見的死鎖案例有哪些?
- 將投資的錢拆封幾份借給借款人,這時(shí)處理業(yè)務(wù)邏輯就要把若干個(gè)借款人一起鎖住 select * from xxx where id in (xx,xx,xx) for update。
- 批量入庫,存在則更新,不存在則插入。解決方法 insert into tab(xx,xx) on duplicate key update
xx='xx'。
如何處理死鎖?
對(duì)待死鎖常見的兩種策略:
- 通過 innodb lock wait_timeout 來設(shè)置超時(shí)時(shí)間,一直等待直到超時(shí);
- 發(fā)起死鎖檢測,發(fā)現(xiàn)死鎖之后,主動(dòng)回滾死鎖中的某一個(gè)事務(wù),讓其它事務(wù)繼續(xù)執(zhí)行。
如何查看死鎖?
- 使用命令
show engine innodb status查看最近的一次死鎖。 - InnoDB Lock Monitor 打開鎖監(jiān)控,每 15s 輸出一次日志。使用完畢后建議關(guān)閉,否則會(huì)影響數(shù)據(jù)庫性能。
如何避免死鎖?
- 為了在單個(gè) InnoDB 表上執(zhí)行多個(gè)并發(fā)寫入操作時(shí)避免死鎖,可以在事務(wù)開始時(shí)通過為預(yù)期要修改的每個(gè)元祖(行)使用 SELECT ... FOR UPDATE 語句來獲取必要的鎖,即使這些行的更改語句是在之后才執(zhí)行的。
- 在事務(wù)中,如果要更新記錄,應(yīng)該直接申請(qǐng)足夠級(jí)別的鎖,即排他鎖,而不應(yīng)先申請(qǐng)共享鎖、更新時(shí)再申請(qǐng)排他鎖,因?yàn)檫@時(shí)候當(dāng)用戶再申請(qǐng)排他鎖時(shí),其他事務(wù)可能又已經(jīng)獲得了相同記錄的共享鎖,從而造成鎖沖突,甚至死鎖
- 如果事務(wù)需要修改或鎖定多個(gè)表,則應(yīng)在每個(gè)事務(wù)中以相同的順序使用加鎖語句。在應(yīng)用中,如果不同的程序會(huì)并發(fā)存取多個(gè)表,應(yīng)盡量約定以相同的順序來訪問表,這樣可以大大降低產(chǎn)生死鎖的機(jī)會(huì)
- 通過 SELECT ... LOCK IN SHARE MODE 獲取行的讀鎖后,如果當(dāng)前事務(wù)再需要對(duì)該記錄進(jìn)行更新操作,則很有可能造成死鎖。
- 改變事務(wù)隔離級(jí)別。
InnoDB 默認(rèn)是如何對(duì)待死鎖的?
InnoDB 默認(rèn)是使用設(shè)置死鎖時(shí)間來讓死鎖超時(shí)的策略,默認(rèn) innodb lock wait_timeout 設(shè)置的時(shí)長是 50s。
如何開啟死鎖檢測?
設(shè)置 innodb deadlock detect 設(shè)置為 on 可以主動(dòng)檢測死鎖,在 Innodb 中這個(gè)值默認(rèn)就是 on 開啟的狀態(tài)。
什么是全局鎖?它的應(yīng)用場景有哪些?
全局鎖就是對(duì)整個(gè)數(shù)據(jù)庫實(shí)例加鎖,它的典型使用場景就是做全庫邏輯備份。
這個(gè)命令可以使整個(gè)庫處于只讀狀態(tài)。使用該命令之后,數(shù)據(jù)更新語句、數(shù)據(jù)定義語句、更新類事務(wù)的提交語句等操作都會(huì)被阻塞。
什么是共享鎖?
共享鎖又稱讀鎖 (read
lock),是讀取操作創(chuàng)建的鎖。其他用戶可以并發(fā)讀取數(shù)據(jù),但任何事務(wù)都不能對(duì)數(shù)據(jù)進(jìn)行修改(獲取數(shù)據(jù)上的排他鎖),直到已釋放所有共享鎖。當(dāng)如果事務(wù)對(duì)讀鎖進(jìn)行修改操作,很可能會(huì)造成死鎖。
什么是排它鎖?
排他鎖 exclusive lock(也叫 writer lock)又稱寫鎖。
若某個(gè)事物對(duì)某一行加上了排他鎖,只能這個(gè)事務(wù)對(duì)其進(jìn)行讀寫,在此事務(wù)結(jié)束之前,其他事務(wù)不能對(duì)其進(jìn)行加任何鎖,其他進(jìn)程可以讀取,不能進(jìn)行寫操作,需等待其釋放。
排它鎖是悲觀鎖的一種實(shí)現(xiàn),在上面悲觀鎖也介紹過。
若事務(wù) 1 對(duì)數(shù)據(jù)對(duì)象 A 加上 X 鎖,事務(wù) 1 可以讀 A 也可以修改 A,其他事務(wù)不能再對(duì) A 加任何鎖,直到事物 1 釋放 A
上的鎖。這保證了其他事務(wù)在事物 1 釋放 A 上的鎖之前不能再讀取和修改 A。排它鎖會(huì)阻塞所有的排它鎖和共享鎖。
使用全局鎖會(huì)導(dǎo)致什么問題?
如果在主庫備份,在備份期間不能更新,業(yè)務(wù)停擺,所以更新業(yè)務(wù)會(huì)處于等待狀態(tài)。
如果在從庫備份,在備份期間不能執(zhí)行主庫同步的 binlog,導(dǎo)致主從延遲。
如何處理邏輯備份時(shí),整個(gè)數(shù)據(jù)庫不能插入的情況?
如果使用全局鎖進(jìn)行邏輯備份就會(huì)讓整個(gè)庫成為只讀狀態(tài),幸好官方推出了一個(gè)邏輯備份工具 MySQLdump 來解決了這個(gè)問題,只需要在使用 MySQLdump
時(shí),使用參數(shù) -single-transaction 就會(huì)在導(dǎo)入數(shù)據(jù)之前啟動(dòng)一個(gè)事務(wù)來保證數(shù)據(jù)的一致性,并且這個(gè)過程是支持?jǐn)?shù)據(jù)更新操作的。
如何設(shè)置數(shù)據(jù)庫為全局只讀鎖?
使用命令 flush tables with read lock(簡稱 FTWRL)就可以實(shí)現(xiàn)設(shè)置數(shù)據(jù)庫為全局只讀鎖。
除了 FTWRL 可以設(shè)置數(shù)據(jù)庫只讀外,還有什么別的方法?
除了使用 FTWRL 外,還可以使用命令 set global readonly=true 設(shè)置數(shù)據(jù)庫為只讀。
FTWRL 和 set global readonly=true 有什么區(qū)別?
FTWRL 和 set global readonly=true 都是設(shè)置整個(gè)數(shù)據(jù)庫為只讀狀態(tài),但他們最大的區(qū)別就是,當(dāng)執(zhí)行 FTWRL
的客戶端斷開之后,整個(gè)數(shù)據(jù)庫會(huì)取消只讀,而 set global readonly=true 會(huì)一直讓數(shù)據(jù)處于只讀狀態(tài)。
如何實(shí)現(xiàn)表鎖?
MySQL 里標(biāo)記鎖有兩種:表級(jí)鎖、元數(shù)據(jù)鎖(meta data lock)簡稱 MDL。表鎖的語法是 lock tables t read/write。
可以用 unlock tables 主動(dòng)釋放鎖,也可以在客戶端斷開的時(shí)候自動(dòng)釋放。lock tables
語法除了會(huì)限制別的線程的讀寫外,也限定了本線程接下來的操作對(duì)象。
對(duì)于 InnoDB 這種支持行鎖的引擎,一般不使用 lock tables 命令來控制并發(fā),畢竟鎖住整個(gè)表的影響面還是太大。
MDL:不需要顯式使用,在訪問一個(gè)表的時(shí)候會(huì)被自動(dòng)加上。
MDL 的作用:保證讀寫的正確性。
在對(duì)一個(gè)表做增刪改查操作的時(shí)候,加 MDL 讀鎖;當(dāng)要對(duì)表做結(jié)構(gòu)變更操作的時(shí)候,加 MDL 寫鎖。
讀鎖之間不互斥,讀寫鎖之間,寫鎖之間是互斥的,用來保證變更表結(jié)構(gòu)操作的安全性。
MDL 會(huì)直到事務(wù)提交才會(huì)釋放,在做表結(jié)構(gòu)變更的時(shí)候,一定要小心不要導(dǎo)致鎖住線上查詢和更新。
悲觀鎖和樂觀鎖有什么區(qū)別?
顧名思義,就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì) block
直到它拿到鎖。正因?yàn)槿绱?,悲觀鎖需要耗費(fèi)較多的時(shí)間,另外與樂觀鎖相對(duì)應(yīng)的,悲觀鎖是由數(shù)據(jù)庫自己實(shí)現(xiàn)了的,要用的時(shí)候,我們直接調(diào)用數(shù)據(jù)庫的相關(guān)語句就可以了。
說到這里,由悲觀鎖涉及到的另外兩個(gè)鎖概念就出來了,它們就是共享鎖與排它鎖。共享鎖和排它鎖是悲觀鎖的不同的實(shí)現(xiàn),它倆都屬于悲觀鎖的范疇。
樂觀鎖是用數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn),這是樂觀鎖最常用的一種實(shí)現(xiàn)方式。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),一般是通過為數(shù)據(jù)庫表增加一個(gè)數(shù)字類型的
version 字段來實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)時(shí),將 version 字段的值一同讀出,數(shù)據(jù)每更新一次,對(duì)此 version 值加
1。當(dāng)我們提交更新的時(shí)候,判斷數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來的version值進(jìn)行比對(duì),如果數(shù)據(jù)庫表當(dāng)前版本號(hào)與第一次取出來的 version
值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
比如: 1、數(shù)據(jù)庫表三個(gè)字段,分別是id、value、version select id,value,version from t where id=#{id} 2、每次更新表中的value字段時(shí),為了防止發(fā)生沖突,需要這樣操作
update t
set value=2,version=version+1
where id=#{id} and version=#{version}
樂觀鎖有什么優(yōu)點(diǎn)和缺點(diǎn)?
因?yàn)闆]有加鎖所以樂觀鎖的優(yōu)點(diǎn)就是執(zhí)行性能高。它的缺點(diǎn)就是有可能產(chǎn)生 ABA 的問題,ABA 問題指的是有一個(gè)變量 V 初次讀取的時(shí)候是 A
值,并且在準(zhǔn)備賦值的時(shí)候檢查到它仍然是 A 值,會(huì)誤以為沒有被修改會(huì)正常的執(zhí)行修改操作,實(shí)際上這段時(shí)間它的值可能被改了其他值,之后又改回為 A
值,這個(gè)問題被稱為 ABA 問題。
InnoDB 存儲(chǔ)引擎有幾種鎖算法?
- Record Lock — 單個(gè)行記錄上的鎖;
- Gap Lock — 間隙鎖,鎖定一個(gè)范圍,不包括記錄本身;
- Next-Key Lock — 鎖定一個(gè)范圍,包括記錄本身。
InnoDB 如何實(shí)現(xiàn)行鎖?
行級(jí)鎖是 MySQL 中粒度最小的一種鎖,他能大大減少數(shù)據(jù)庫操作的沖突。
INNODB 的行級(jí)鎖有共享鎖(S LOCK)和排他鎖(X
LOCK)兩種。共享鎖允許事物讀一行記錄,不允許任何線程對(duì)該行記錄進(jìn)行修改。排他鎖允許當(dāng)前事物刪除或更新一行記錄,其他線程不能操作該記錄。
共享鎖:SELECT ... LOCK IN SHARE MODE,MySQL
會(huì)對(duì)查詢結(jié)果集中每行都添加共享鎖,前提是當(dāng)前線程沒有對(duì)該結(jié)果集中的任何行使用排他鎖,否則申請(qǐng)會(huì)阻塞。
排他鎖:select * from t where id=1 for update,其中 id 字段必須有索引,MySQL
會(huì)對(duì)查詢結(jié)果集中每行都添加排他鎖,在事物操作中,任何對(duì)記錄的更新與刪除操作會(huì)自動(dòng)加上排他鎖。前提是當(dāng)前沒有線程對(duì)該結(jié)果集中的任何行使用排他鎖或共享鎖,否則申請(qǐng)會(huì)阻塞。
優(yōu)化鎖方面你有什么建議?
- 盡量使用較低的隔離級(jí)別。
- 精心設(shè)計(jì)索引, 并盡量使用索引訪問數(shù)據(jù), 使加鎖更精確, 從而減少鎖沖突的機(jī)會(huì)。
- 選擇合理的事務(wù)大小,小事務(wù)發(fā)生鎖沖突的幾率也更小。
- 給記錄集顯示加鎖時(shí),最好一次性請(qǐng)求足夠級(jí)別的鎖。比如要修改數(shù)據(jù)的話,最好直接申請(qǐng)排他鎖,而不是先申請(qǐng)共享鎖,修改時(shí)再請(qǐng)求排他鎖,這樣容易產(chǎn)生死鎖。
- 不同的程序訪問一組表時(shí),應(yīng)盡量約定以相同的順序訪問各表,對(duì)一個(gè)表而言,盡可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機(jī)會(huì)。
- 盡量用相等條件訪問數(shù)據(jù),這樣可以避免間隙鎖對(duì)并發(fā)插入的影響。
- 不要申請(qǐng)超過實(shí)際需要的鎖級(jí)別。
- 除非必須,查詢時(shí)不要顯示加鎖。 MySQL 的 MVCC 可以實(shí)現(xiàn)事務(wù)中的查詢不用加鎖,優(yōu)化事務(wù)性能;MVCC 只在 COMMITTED READ(讀提交)和 REPEATABLE READ(可重復(fù)讀)兩種隔離級(jí)別下工作。
- 對(duì)于一些特定的事務(wù),可以使用表鎖來提高處理速度或減少死鎖的可能。