MyISAM加鎖分析


為什么加鎖

你正在讀着你喜歡的女孩遞給你的信,看到一半的時候,她的好閨蜜過來瞄了一眼(假設她會隱身術,你看不到她),她想把“我很喜歡你”改成“我不喜歡你”,剛把“很”字擦掉,“不”字還沒寫完,只寫了一橫一撇,這時候你正讀到這個字,她怕你察覺到也就沒繼續往下寫了,這時候你讀到的這句話就是“我丆喜歡你”,這是什么鬼?!這位閨蜜樂了:沒錯,確實是鬼在整蠱你呢,嘿嘿!

數據庫也會鬧鬼嗎?很有可能!假設會話1正在讀取表里的一條記錄(還沒讀取完),另一個會話2突然插隊過來更新表里的同一條記錄(還沒更新完),那么會話1拿到的數據就可能是錯誤的(還沒更新完的內容和原內容混在一起,造成亂碼,就像上面的“我丆喜歡你”)。

怎么避免這種情況呢?加鎖,當有一個人在讀的時候,別人能讀不能寫,當有一個人在寫的時候,別人不能讀和寫。

所以,加鎖是為了在並發操作的時候,能夠確保數據的完整性和一致性。

加鎖的規則

MyISAM鎖的粒度是表級鎖,在執行查詢(SELECT)之前,嘗試在表上面加讀鎖,在執行更新(UPDATE,DELETE,INSERT)之前,嘗試在表上面加寫鎖。

加寫鎖:

如果在表上沒有鎖(讀鎖和寫鎖),在它上面放一個寫鎖。
否則,把鎖定請求放在寫鎖定隊列中。

加讀鎖:

如果在表上沒有寫鎖定,把一個讀鎖定放在它上面。
否則,把鎖定請求放在讀鎖定隊列中。

優先級:

當一個鎖定被釋放時,鎖定優先被寫鎖定隊列中的線程得到,然后是讀鎖定隊列中的線程。這意味着如果有大量的寫操作,讀操作將會一直等待,直到寫完成。可以通過以下命令看到加鎖的情況:

SHOW STATUS LIKE 'table%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 42    |
| Table_locks_waited    | 3     |
+-----------------------+-------+

Table_locks_immediate是加鎖立刻執行成功的次數,Table_locks_waited是造成等待的加鎖次數。另外,可以通過LOW_PRIORITY來改變優先級

實例分析

開一個會話窗口1,輸入下面的語句執行:

CREATE TABLE `users`(
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(15) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=MYISAM DEFAULT CHARSET=utf8 COMMENT='用戶';

INSERT INTO `users` VALUES (null, 'pigfly'),(null,'zhupp');

為了模擬,我們手動執行LOCK TABLES語句把表鎖住:

LOCK TABLES `users` READ LOCAL;
SELECT * FROM `users`;
UPDATE `users` SET name='aa' where id=1;

SELECT正常返回,UPDATE報錯了,原因是當前表加了讀鎖,則當前會話只能執行讀操作,不能執行更新操作。

新開一個會話窗口2:

INSERT INTO `users` VALUES (null, 'zhupp');
UPDATE `users` SET name='xxx' where id=1;

可以看到插入執行成功,但是UPDATE操作被窗口1加的讀鎖阻塞了,我們回到窗口1執行:

UNLOCK TABLES;

這時候窗口2的更新語句馬上返回更新成功了。

為什么插入不會被讀鎖阻塞呢?原因是當表加了讀鎖並且表不存在空閑塊的時候(刪除或者更新表中間的記錄會導致空閑塊,OPTIMIZE TABLE可以清除空閑塊),MYISAM默認允許其他線程從表尾插入。可以通過改變系統變量concurrent_insert(並發插入)的值來控制並發插入的行為。

SHOW VARIABLES LIKE 'concurrent%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| concurrent_insert | AUTO  |
+-------------------+-------+

Value的值:

  • NEVER(0): 不允許並發插入
  • AUTO(1): 表里沒有空行時允許從表尾插入(默認)
  • ALWAYS(2): 任何時候都允許並發插入

注意:鎖表的時候加了LOCAL關鍵字表示允許走並發插入的邏輯,具體是否可以並發插入還需要看是否滿足concurrent_insert指定的條件,只有手動鎖表的時候才需要指定LOCAL關鍵字。

測試一下當表里有空閑塊的情況,窗口1執行:

DELETE FROM `users` WHERE id=1;
LOCK TABLES `users` READ LOCAL;

然后在窗口2執行:

INSERT INTO `users` VALUES (null, 't1');

果然被阻塞了。我們把並發插入的值改成2試試,在窗口1執行:

UNLOCK TABLES;
SET GLOBAL concurrent_insert=2;
DELETE FROM `users` WHERE id=2;
LOCK TABLES `users` READ LOCAL;

然后在窗口2執行:

INSERT INTO `users` VALUES (null, 't2');
SELECT * FROM `users`;

這一次沒有被阻塞,插入成功了。

表級鎖的特點

開銷小、加鎖快、不會產生死鎖,鎖定力度大,發生鎖沖突的概率最高,不適合高並發場景。

性能優化

  1. 對於並發插入,一般默認配置AUTO就可以了,如果有大量插入操作,可以把concurrent_insert設置為2,然后定期在流量低峰期執行OPTIMIZE TABLE來清除空閑塊。
  2. 調整優先級。
  3. 在大量更新操作前手動鎖表,這樣鎖表只執行了一次,不然每執行一次更新就鎖一次表。
  4. 存在大量更新操作造成等待,又要兼顧查詢的時候,給max_write_lock_count設置一個低值,在寫鎖達到一定數量時允許執行掛起的讀請求。

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM