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