MySQL 鎖
MySQL 的鎖機制,主要用於高並發場景下,比較重要的知識點是保證數據的一致性的和事務隔離性,在高並發下控制並發訪問。
鎖的種類
MySQL 中鎖的分類按照不同類型的划分可以分成不同的鎖,
- 按照鎖的粒度:
- 表鎖:粒度最大的鎖,開銷小,加鎖快,不會出現死鎖,但是由於粒度太大,因此造成鎖的沖突幾率大,並發性能低。常見的有 MyISAM 儲存引擎就支持表鎖,MyISAM的表鎖模式有兩種:表共享讀鎖和表獨占寫鎖
當一個線程獲取到 MyISAM 表的讀鎖的時候,會阻塞其他用戶對該表的寫操作,但是不會阻塞其它用戶對該用戶的讀操作。
相反的,當一個線程獲取到 MyISAM 表的寫鎖的時候,就會阻塞其它用戶的讀寫操作對其它的線程具有排它性。
- 頁鎖:粒度是介於行鎖和表鎖之間的一種鎖,頁鎖是在 BDB 中支持的一種鎖機制,很少提及和使用
- 行鎖:粒度最小的鎖機制,行鎖的加鎖開銷性能大,加鎖慢,並且會出現死鎖,但是行鎖的鎖沖突的幾率低,並發性能高。
行鎖是 InnoDB 默認的支持的鎖機制,MyISAM 不支持行鎖,這個也是 InnoDB 和 MyISAM 的區別之一。
行鎖在使用的方式上可以划分為:共享讀鎖( S 鎖)和排它寫鎖( X 鎖)。
當一個事務對 MySQL 中的一條數據行加上了 S 鎖,當前事務不能修改該行數據只能執行讀操作,其他事務只能對該行數據加 S 鎖不能加 X 鎖。若是一個事務對一行數據加了 X 鎖,該事務能夠對該行數據執行讀和寫操作,其它事務不能對該行數據加任何的鎖,既不能讀也不能寫。
- 表鎖:粒度最大的鎖,開銷小,加鎖快,不會出現死鎖,但是由於粒度太大,因此造成鎖的沖突幾率大,並發性能低。常見的有 MyISAM 儲存引擎就支持表鎖,MyISAM的表鎖模式有兩種:表共享讀鎖和表獨占寫鎖
- 按照使用的方式:共享鎖和排它鎖;
- 按照思想:數據庫管理系統中為了控制並發,保證在多個事務執行時的數據一致性以及事務的隔離性,使用悲觀鎖和樂觀鎖來解決並發場景下的問題。
- 樂觀鎖:需要程序員自己去實現的鎖機制
- 悲觀鎖:悲觀鎖的實現是基於Mysql自身的鎖機制實現
悲觀鎖和樂觀鎖是在很多框架都存在的一種思想,不要狹義地認為它們是某一種框架的鎖機制。
MyISAM
MyISAM中 默認支持的表級鎖有兩種:共享讀鎖和獨占寫鎖。表級鎖在 MyISAM 和 InnoDB 的存儲引擎中都支持,但是 InnoDB 默認支持的是行鎖。
MySQL 中平時讀寫操作都是隱式的進行加鎖和解鎖操作,MySQL 已經自動實現加鎖和解鎖的操作,若是想要測試鎖機制,就要顯示的自己控制鎖機制。
MySQL 中可以通過以下 sql 來顯示的在事務中顯式的進行加鎖和解鎖操作:
-- 顯式的添加表級讀鎖 LOCK TABLE 表名 READ -- 顯示的添加表級寫鎖 LOCK TABLE 表名 WRITE -- 顯式的解鎖(當一個事務commit的時候也會自動解鎖) unlock tables;
MyISAM 表級寫鎖

CREATE TABLE IF NOT EXISTS employee ( id INT PRIMARY KEY auto_increment, name VARCHAR(40), money INT )ENGINE MyISAM INSERT INTO employee(name, money) VALUES('黎杜', 1000); INSERT INTO employee(name, money) VALUES('非科班的科班', 2000);

LOCK TABLE employee WRITE

select * from employee # 阻塞
第一個程序可以任意讀寫該表,但其他程序不行。只有在鎖被釋放時,別的程序才能對該表進行操作
MyISAM表級共享讀鎖
測試數據同上

LOCK TABLEemployee read;
第一個程序進行插入、更新數據,發現都會報錯,只能查詢數據。
其他程序也無法進行插入、更新數據,但是可以查詢數據。
MyISAM表級鎖競爭情況
MyISAM存儲引擎中,可以通過查詢變量來查看並發場景鎖的爭奪情況
show status like 'table_locks%';
通過 table_locks_waited
和 table_locks_immediate
的值的大小分析鎖的競爭情況。
Table_locks_immediate
:表示能夠立即獲得表級鎖的鎖請求次數;Table_locks_waited
表示不能立即獲取表級鎖而需要等待的鎖請求次數分析,值越大競爭就越嚴重。
並發插入
上述例子中的加鎖和釋放鎖都是在 MySQL 中已經實現了的隱式的操作,實際並不會這么做的。
MyISAM 存儲引擎中,雖然讀寫操作是串行化的,但是它也支持並發插入,這個需要設置內部變量 concurrent_insert
的值。它的值有三個值0、1、2
。可以通過以下的 sql 查看 concurrent_insert
的默認值為AUTO(或者1)。
- 值為
NEVER (or 0)
表示不支持比並發插入; - 值為
AUTO (或者1)
表示在MyISAM表中沒有被刪除的行,運行另一個線程從表尾插入數據; - 值為
ALWAYS (or 2)
表示不管是否有刪除的行,都允許在表尾插入數據。
show variable like '%concurrent_insert';
鎖調度
MyISAM 存儲引擎中,假如同時一個讀請求,一個寫請求過來的話,會優先處理寫請求,因為 MyISAM 存儲引擎中認為寫請求比讀請求重要。這樣就會導致,假如大量的讀寫請求過來,就會導致讀請求長時間的等待,或者"線程餓死",因此 MyISAM 不適合運用於大量讀寫操作的場景,這樣會導致長時間讀取不到用戶數據,用戶體驗感極差。
可以通過設置low-priority-updates
參數,設置請求鏈接的優先級,使得 MySQL 優先處理讀請求。
InnoDB
InnoDB 和 MyISAM 不同的是,InnoDB 支持行鎖和事務
InnoDB 中除了有表鎖和行級鎖的概念,還有 Gap Lock(間隙鎖)、Next-key Lock鎖,間隙鎖主要用於范圍查詢的時候,鎖住查詢的范圍,並且間隙鎖也是解決幻讀的方案。
InnoDB 中的行級鎖是對索引加的鎖,在不通過索引查詢數據的時候,InnoDB 就會使用表鎖。但是通過索引查詢的時候是否使用索引,還要看 MySQL 的執行計划,MySQL 的優化器會判斷是一條 sql 執行的最佳策略。若是 MySQL 覺得執行索引查詢還不如全表掃描速度快,那么 MySQL 就會使用全表掃描來查詢,這是即使 sql 語句中使用了索引,最后還是執行為全表掃描,加的是表鎖。
InnoDB 的行鎖與表鎖
InnoDB的行鎖也是分為行級共享讀鎖(S鎖)和排它寫鎖(X鎖),原理特點和 MyISAM 的表級鎖兩種模式是一樣的。
- 執行非索引條件查詢執行的是表鎖。
- 執行索引查詢是否是加行鎖,還得看 MySQL 的執行計划,可以通過 explain 關鍵字來查看。
- 用普通鍵索引的查詢,遇到索引值相同的,也會對其他的操作數據行的產生影響。
InnoDB 的間隙鎖
當使用范圍條件查詢而不是等值條件查詢的時候,InnoDB 就會給符合條件的范圍索引加鎖,在條件范圍內並不存的記錄就叫做"間隙(GAP)"
在事務的四大隔離級別中,不可重復讀會產生幻讀的現象,只能通過提高隔離級別到串行化來解決幻讀現象。但是 MySQL 中的不可重復是已經解決了幻讀問題,它通過引入間隙鎖的實現來解決幻讀,通過給符合條件的間隙加鎖,防止再次查詢的時候出現新數據產生幻讀的問題。
- 主鍵索引不需要間隙鎖——主鍵索引具有唯一性,不允許出現重復,那么當進行等值查詢的時候id=3,只能有且只有一條數據,是不可能再出現id=3的第二條數據。因此它只要鎖定這條數據(鎖定索引),在下次查詢當前讀的時候不會被刪除、或者更新id=3的數據行,也就保證了數據的一致性,所以主鍵索引由於他的唯一性的原因,是不需要加間隙鎖的。
- 范圍查詢會加上間隙鎖
- 是用不存在的檢索條件,會使用間隙鎖
死鎖
死鎖在 InnoDB 中才會出現死鎖,MyISAM 是不會出現死鎖,因為 MyISAM支持的是表鎖,一次性獲取了所有的鎖,其它的線程只能排隊等候。而 InnoDB 默認支持行鎖,獲取鎖是分步的,並不是一次性獲取所有的鎖,因此在鎖競爭的時候就會出現死鎖的情況。雖然 InnoDB 會出現死鎖,但是並不影響 InnoDB 成為最受歡迎的存儲引擎,MyISAM 可以理解為串行化操作,讀寫有序,因此支持的並發性能低下。

當一個事務開始並且update一條id=1的數據行時,成功獲取到寫鎖,此時另一個事務執行也update另一條id=2的數據行時,也成功獲取到寫鎖(id為主鍵)。 此時cpu將時間分配給了事務一,事務一接着也是update id=2的數據行,因為事務二已經獲取到id=2數據行的鎖,所以事務已處於等待狀態。 事務二有獲取到了時間,像執行update id=1的數據行,但是此時id=1的鎖被事務一獲取到了,事務二也處於等待的狀態,因此形成了死鎖。
死鎖的解決方案
要解決死鎖問題,在程序的設計上,當發現程序有高並發的訪問某一個表時,盡量對該表的執行操作串行化,或者鎖升級,一次性獲取所有的鎖資源。也可以設置參數innodb_lock_wait_timeout
,超時時間,並且將參數innodb_deadlock_detect
打開,當發現死鎖的時候,自動回滾其中的某一個事務。
總結
MyISAM的表鎖分為兩種模式:「共享讀鎖」和「排它寫鎖」。獲取的讀鎖的線程對該數據行只能讀,不能修改,其它線程也只能對該數據行加讀鎖。獲取到寫鎖的線程對該數據行既能讀也能寫,對其他線程對該數據行的讀寫具有排它性。
MyISAM中默認寫優先於去操作,因此MyISAM一般不適合運用於大量讀寫操作的程序中。
InnoDB的行鎖雖然會出現死鎖的可能,但是InnoDB的支持的並發性能比MyISAM好,行鎖的粒度最小,一定的方法和措施可以解決死鎖的發生,極大的發揮InnoDB的性能。
InnoDB中引入了間隙鎖的概念來決解出現幻讀的問題,也引入事務的特性,通過事務的四種隔離級別,來降低鎖沖突,提高並發性能。