MySQL 中的表級鎖很差勁嗎?
當(dāng)然不是!
其實本來今天是要和大家聊一聊 MyISAM 引擎的,結(jié)果在寫的過程中,又發(fā)現(xiàn)鎖這個話題可以單獨寫一篇,于是就有了今天的文章。
說到 MyISAM 和 InnoDB 的區(qū)別,很多人都知道,區(qū)別在于一個是表鎖一個是行鎖,那么小伙伴們有沒有想過,表鎖和行鎖有什么區(qū)別?各自又有哪些玩法?今天松哥就來和大家聊聊這個話題。
1.鎖
我們先來大致說一下 MySQL 中的鎖。
當(dāng)多個事務(wù)或者多個進(jìn)程訪問同一個資源的時候,為了保證數(shù)據(jù)的一致性,就需要用到 MySQL 鎖機制,從鎖定資源的角度來看,MySQL 中的鎖大致上可以分為三種:
- 表級鎖(table-level locking):表級鎖的特點是開銷小,加鎖快,不會出現(xiàn)死鎖,但是鎖定粒度較大,發(fā)生鎖沖突的概率高,而且并發(fā)度也低。
- 行級鎖(row-level locking):行級鎖的特點是開銷大,加鎖慢,有可能會出現(xiàn)死鎖,但是它的鎖定粒度小,發(fā)生鎖沖突的概率低,并發(fā)度也高。
- 頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間,會出現(xiàn)死鎖,鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。
雖然理論上有三種鎖,但是對于在坐的各位小伙伴包括松哥而言,我們?nèi)粘i_發(fā)接觸最多的還是前兩種,就是表級鎖和行級鎖。
在 MySQL 中,MyISAM 引擎是表級鎖,而 InnoDB 引擎則支持行級鎖,不過需要注意,其實 InnoDB 也支持表級鎖,只不過默認(rèn)情況下是行級鎖。
2.表級鎖
MySQL 的表級鎖有兩種模式:
表共享讀鎖(Table Read Lock)。
表獨占寫鎖(Table Write Lock)。
MyISAM 引擎在執(zhí)行 select 時會自動給相關(guān)表加讀鎖,在執(zhí)行 update、delete 和 insert 時會自動給相關(guān)表加寫鎖。
2.1 表共享讀鎖
我們先來看表共享讀鎖,加了共享讀鎖的表,不會阻塞其他 session 的讀請求,但是會阻塞其他 session 的寫請求。
我們來演示一下這個效果。
在下面的案例中,我們會準(zhǔn)備兩個窗口,代表兩個 session。
首先我們新建一張表,選擇 MyISAM 作為存儲引擎,DDL 如下:
- CREATE TABLE `user` (
- `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
- `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
添加一條測試數(shù)據(jù):
- insert into user(name) values('javaboy');
然后我們在兩個命令行窗口分別登錄 mysql,模擬出兩個 session。
首先在第一個窗口為表添加讀鎖,如下:
- lock table user read;
然后在第二個窗口去讀取數(shù)據(jù):
- select * from user;
可以發(fā)現(xiàn),是可以正常讀取的。
然后我們嘗試在第二個窗口寫入一條數(shù)據(jù):
- insert into user(name) values('itboyhub');
這條寫入語句會 卡住,如下:
卡住的原因是因為 user 表目前被上了共享讀鎖,這個時候需要我們?nèi)サ降谝粋€窗口中,解除表的鎖定,這個時候第二個窗口中的這條插入 sql 就可以執(zhí)行了。如下:
- unlock tables;
當(dāng)這條 SQL 完畢后,第二個窗口中的插入語句立馬就執(zhí)行了。
如下是窗口2的截圖:
可以看到,加了共享讀鎖的表,不會阻塞其他 session 的讀(select)請求,但是會阻塞其他 session 的寫(insert、update、delete)請求。
需要注意的是,如果在同一條 SQL 中,同一個表名出線了 N 次,該表就要鎖定 N 次,如下:
思考:
我們在窗口 1 中給 user 表加了鎖,那么在窗口 1 中是否可以對 user 表執(zhí)行 insert/update/delete 等寫操作呢?評論區(qū) show 出你的答案~
2.2 表獨占寫鎖
這個獨占寫鎖就是大家鎖所熟知的排他鎖,它會阻塞其他進(jìn)程對同一表的讀寫操作,只有當(dāng)當(dāng)前鎖釋放后,才會執(zhí)行其他進(jìn)程的讀寫操作。
我們來演示一下這個過程。
還是兩個窗口,首先我們我們在第一個窗口中執(zhí)行鎖表操作:
- lock table user write;
然后去第二個窗口中做查詢操作,如下:
可以看到,由于是排他鎖,所以查詢操作也被阻塞了。此時需要在窗口 1 中解除表的鎖定,窗口 2 中的查詢操作才會繼續(xù)執(zhí)行下去。
這就是表獨占寫鎖,也就是排他鎖。
在 MyISAM 存儲引擎中,會自動為 SELECT 語句加上共享鎖,為 update/delete/insert 操作加上排他鎖。
2.3 concurrent_insert
前面我們講的是表級鎖的兩種基本模式,在具體的使用過程中,我們還可以通過 concurrent_insert 去配置一些并發(fā)行為。
concurrent_insert 有三種不同的取值:
NEVER:加了讀鎖之后,不允許其他 session 并發(fā)插入。
AUTO:加了讀鎖之后,如果表里沒有刪除過數(shù)據(jù),其他 session 就可以并發(fā)插入。
ALWAYS:加了讀鎖之后,允許其他 session 并發(fā)插入。
需要注意的是,在 MySQL5.5.3 之前,NEVER、AUTO 以及 ALWAYS 分別使用 0、1、2 代替。
通過 show global variables like '%concurrent_insert%' 命令我們可以查看當(dāng)前數(shù)據(jù)庫中 concurrent_insert 的取值,如下:
可以看到,數(shù)據(jù)庫中默認(rèn)的 concurrent_insert 取值為 AUTO。有小伙伴可能會說,啥?AUTO?那為啥我在 2.1 小結(jié)中,當(dāng)表加了讀鎖之后,其他 session 無法插入數(shù)據(jù)呢?這其實跟加鎖方式有關(guān),我們一起來看下。
還是兩個窗口,首先我們在第一個窗口中為表添加讀鎖,如下:
- lock table user read local;
可以看到,最后多了一個 local,這就是關(guān)鍵。
接下來我們在窗口 2 中去嘗試讀寫操作,如下:
從圖中可以看到,讀寫操作都可以順利執(zhí)行。
但是這個時候,如果我們?nèi)ゴ翱?1 中執(zhí)行查詢,如下:
可以看到,這里并看不到窗口 2 中剛剛添加的那條數(shù)據(jù),換句話說,窗口 2 中添加的數(shù)據(jù)對窗口 1 是不可以見的,必須等窗口 1 中的鎖釋放之后,才可以看到窗口 2 中添加的數(shù)據(jù)。
如下圖,釋放鎖之后,就可以看到另外一個窗口添加進(jìn)來的數(shù)據(jù)了:
這是我給大家演示的默認(rèn)的 concurrent_insert 的行為,大家也可以通過如下 SQL 修改該值:
- set global concurrent_insert = ALWAYS;
2.4 鎖的優(yōu)先級
在 MyISAM 中,默認(rèn)情況下,寫鎖的優(yōu)先級要高,不過開發(fā)者也可以自行調(diào)整這個默認(rèn)鎖的優(yōu)先級。
話說回來,由于 MyISAM 是表鎖,所以不建議用在需要頻繁更新的場景下,否則可能會造成長時間的鎖等待。所以下面的優(yōu)先級調(diào)整,僅僅作為技術(shù)層面的探討。
修改 SQL 優(yōu)先級
首先我們可以在執(zhí)行 SQL 的時候,順便修改其優(yōu)先級:
例如執(zhí)行 select 的時候可以使用 HIGH_PRIORITY 來提高該語句的優(yōu)先級,如下:
在執(zhí)行 delete/update/insert 等操作的時候,可以使用 LOW_PRIORITY 來降低其優(yōu)先級,以便讓讀取操作先執(zhí)行:
當(dāng)然我們也可以通過如下 SQL 讓所有支持 LOW_PRIORITY 選項的語句都默認(rèn)地按照低優(yōu)先級來處理。
- set LOW_PRIORITY_UPDATES = 1
修改寫鎖上限
我們可以修改 MAX_WRITE_LOCK_COUNT 的值,該變量默認(rèn)值如下圖:
這個值表示當(dāng)一個表的寫鎖數(shù)量達(dá)到給定的值后,就降低寫鎖的優(yōu)先級,讓讀鎖有機會執(zhí)行。如果有需要,我們可以自行調(diào)整這個值,調(diào)整方式如下:
- set GLOBAL MAX_WRITE_LOCK_COUNT=1024;
3.行級鎖
行級鎖松哥留到講 InnoDB 的時候再和大家聊,今天我們就先扯這么多~
參考資料:
1.https://database.51cto.com/art/201910/604421.htm
2.https://zhuanlan.zhihu.com/p/123962424
本文轉(zhuǎn)載自微信公眾號「江南一點雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系江南一點雨公眾號。