說到鎖機制以前,先來看看Mysql的存儲引擎,畢竟不一樣的引擎的鎖機制也隨着不一樣。mysql
MyIsam :不支持事務,不支持外鍵,因此訪問速度快。鎖機制是表鎖,支持全文索引sql
InnoDB :支持事務、支持外鍵,因此對比MyISAM,InnoDB的處理效率差一些,並要佔更多的磁盤空間保留數據和索引。鎖機制是行鎖,不支持全文索引數據庫
Memory:數據是存放在內存中的,默認哈希索引,很是適合存儲臨時數據,服務器關閉後,數據會丟失掉。segmentfault
MyISAM:應用是以讀操做和插入操做爲主,只有不多的更新和刪除操做,而且對事務的完整性、併發性要求不是很高。安全
InnoDB:用於事務處理應用程序,支持外鍵,若是應用對事務的完整性有比較高的要求,在併發條件下要求數據的一致性。更新刪除等頻繁(InnoDB能夠有效的下降因爲刪除和更新致使的鎖定),對於數據準確性要求比較高的,此引擎適合。服務器
Memory:一般用於更新不太頻繁的小表,用以快速獲得訪問結果。多線程
若是熟悉多線程,那麼對鎖確定是有概念的,鎖是計算機協調多個進程或線程對某一資源併發訪問的機制。併發
Mysql中的鎖分爲表鎖和行鎖:spa
顧名思義,表鎖就是鎖住一張表,而行鎖就是鎖住一行。線程
表鎖的特色:開銷小,不會產生死鎖,發生鎖衝突的機率高,而且併發度低。
行鎖的特色:開銷大,會產生死鎖,發生鎖衝突的機率低,併發度高。
所以MyISAM和Memory引擎採用的是表鎖,而InnoDB存儲引擎採用的是行鎖。
分爲共享讀鎖和獨佔寫鎖。
讀鎖是:當某一進程對某張表進行讀操做時(select),其餘線程也能夠讀,可是不能寫。簡單的理解就是,我讀的時候你不能寫。
寫鎖是:當某一進程對某種表某張表的寫時(insert,update,,delete),其餘線程不能寫也不能讀。能夠理解爲,我寫的時候,你不能讀,也不能寫。
所以MyISAM的讀操做和寫操做,以及寫操做之間是串行的!MyISAM在執行讀寫操做的時候會自動給表加相應的鎖(也就是說不用顯示的使用lock table命令),MyISAM老是一次得到SQL語句所須要的所有鎖,這也是MyISAM不會出現死鎖的緣由。
下面分別舉關於寫鎖和讀鎖的例子:
寫鎖:
事務1 | 事務2 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
取得first_test表的寫鎖:mysql> lock table first_test write;Query OK, 0 rows affected (0.00 sec) | |||||||||||||||||||
當前事務對查詢、更新和插入操做均可以執行mysql> select * from first_test ;+----+------+ | id | age | +----+------+ | 1 | 10 | 2 | 11 | 3 | 12 | 4 | 13 | +----+------+4 rows in set (0.00 sec)mysql> insert into first_test(age) values(14);Query OK, 1 row affected (0.11 sec) | 其餘事務對鎖定表的查詢被阻塞,須要等到鎖被釋放,才能夠執行mysql> select * from first_test;等待...... | ||||||
mysql> unlock table;Query OK, 0 rows affected (0.00 sec) | 等待 | ||||||||||||||||||
mysql> select * from first_test;+----+------+ | id | age | +----+------+ | 1 | 10 | 2 | 11 | 3 | 12 | 4 | 13 | 5 | 14 | +----+------+5 rows in set (9 min 45.02 sec) |
讀鎖例子以下:
事務1 | 事務2 | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
得到表first_read的鎖定mysql> lock table first_test read;Query OK, 0 rows affected (0.00 sec) | |||||||||||||||||||||||||||||||||||||
當前事務能夠查詢該表記錄:mysql> select * from first_test;+----+------+ | id | age | +----+------+ | 1 | 10 | 2 | 11 | 3 | 12 | 4 | 13 | 5 | 14 | +----+------+5 rows in set (0.00 sec) | 其餘事務也能夠查到該表信息mysql> select * from first_test;+----+------+ | id | age | +----+------+ | 1 | 10 | 2 | 11 | 3 | 12 | 4 | 13 | 5 | 14 | +----+------+5 rows in set (0.00 sec) | ||||||||
可是當前事務不能查詢沒有鎖定的表:mysql> select * from goods;ERROR 1100 (HY000): Table 'goods' was not locked with LOCK TABLES | 其餘事務能夠查詢或更新未鎖定的表:mysql> select * from goods;+----+------------+------+ | id | name | num | +----+------------+------+ | 1 | firstGoods | 11 | 3 | ThirdGoods | 11 | 4 | fourth | 11 | +----+------------+------+10 rows in set (0.00 sec) | ||||||||||||||||||||||
並且插入更新鎖定的表都會報錯:mysql> insert into first_test(age) values(15);ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updatedmysql> update first_test set age=100 where id =1;ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updated | 當更新被鎖定的表時會等待:mysql> update first_test set age=100 where id =1;等待...... | ||||||||||||||||||||||||||||||||||||
mysql> unlock table;Query OK, 0 rows affected (0.00 sec) | mysql> update first_test set age=100 where id =1;Query OK, 1 row affected (38.82 sec)Rows matched: 1 Changed: 1 Warnings: 0 |
剛說到Mysql在插入和修改的時候都是串行的,可是MyISAM也支持查詢和插入的併發操做。
MyISAM中有一個系統變量concurrent_insert(默認爲1),用以控制併發插入(用戶在表尾插入數據)行爲。
當concurrent_insert爲0時,不容許併發插入。
當concurrent_insert爲1時,若是表中沒有空洞(中間沒有被刪除的行),MyISAM容許一個進程在讀表的同時,另外一個進程從表尾插入記錄。
當concurrent_insert爲2時,不管MyISAM表中有沒有空洞,均可以在末尾插入記錄
事務1 | 事務2 | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mysql> lock table first_test read local;Query OK, 0 rows affected (0.00 sec)--加入local選項是說明,在表知足併發插入的前提下,容許在末尾插入數據 | |||||||||||||||||||||||||
當前進程不能進行插入和更新操做mysql> insert into first_test(age) values(15);ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updatedmysql> update first_test set age=200 where id =1;ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updated | 其餘進程能夠進行插入,可是更新會等待:mysql> insert into first_test(age) values(15);Query OK, 1 row affected (0.00 sec)mysql> update first_test set age=200 where id =2;等待..... | ||||||||||||||||||||||||
當前進程不能不能訪問其餘進程插入的數據mysql> select * from first_test;+----+------+ | id | age | +----+------+ | 1 | 100 | 2 | 11 | 3 | 12 | 4 | 13 | 5 | 14 | 6 | 14 | +----+------+6 rows in set (0.00 sec) | |||||||||
釋放鎖之後皆大歡喜mysql> unlock table;Query OK, 0 rows affected (0.00 sec) | 等待 | ||||||||||||||||||||||||
插入的和更新的都出來的:mysql> select * from first_test;+----+------+ | id | age | +----+------+ | 1 | 100 | 2 | 200 | 3 | 12 | 4 | 13 | 5 | 14 | 6 | 14 | 7 | 15 | +----+------+7 rows in set (0.00 sec) | mysql> update first_test set age=200 where id =2;Query OK, 1 row affected (1 min 39.75 sec)Rows matched: 1 Changed: 1 Warnings: 0 |
須要注意的:
併發插入是解決對同一表中的查詢和插入的鎖爭用。
若是對有空洞的表進行併發插入會產生碎片,因此在空閒時能夠利用optimize table命令回收因刪除記錄產生的空洞。
在MyISAM中當一個進程請求某張表的讀鎖,而另外一個進程同時也請求寫鎖,Mysql會先讓後者得到寫鎖。即便讀請求比寫請求先到達鎖等待隊列,寫鎖也會插入到讀鎖以前。
由於Mysql老是認爲寫請求通常比讀請求重要,這也就是MyISAM不太適合有大量的讀寫操做的應用的緣由,由於大量的寫請求會讓查詢操做很難獲取到讀鎖,有可能永遠阻塞。
處理辦法:
一、指定Insert、update、delete語句的low_priority屬性,下降其優先級。
二、指定啓動參數low-priority-updates,使得MyISAM默認給讀請求優先的權利。
三、執行命令set low_priority_updates=1,使該鏈接發出的請求下降。
四、指定max_write_lock_count設置一個合適的值,當寫鎖達到這個值後,暫時下降寫請求的優先級,讓讀請求獲取鎖。
可是上面的處理辦法形成的緣由就是當遇到複雜的查詢語句時,寫請求可能很難獲取到鎖,這是一個很糾結的問題,因此咱們通常避免使用複雜的查詢語句,若是如法避免,則能夠再數據庫空閒階段(深夜)執行。
咱們知道mysql在之前,存儲引擎默認是MyISAM,可是隨着對事務和併發的要求愈來愈高,便引入了InnoDB引擎,它具備支持事務安全等一系列特性。
InnoDB實現了兩種類型的行鎖。
共享鎖(S):容許一個事務去讀一行,阻止其餘事務得到相同的數據集的排他鎖。
排他鎖(X):容許得到排他鎖的事務更新數據,可是組織其餘事務得到相同數據集的共享鎖和排他鎖。
能夠這麼理解:
共享鎖就是我讀的時候,你能夠讀,可是不能寫。排他鎖就是我寫的時候,你不能讀也不能寫。其實就是MyISAM的讀鎖和寫鎖,可是針對的對象不一樣了而已。
除此以外InnoDB還有兩個表鎖:
意向共享鎖(IS):表示事務準備給數據行加入共享鎖,也就是說一個數據行加共享鎖前必須先取得該表的IS鎖
意向排他鎖(IX):相似上面,表示事務準備給數據行加入排他鎖,說明事務在一個數據行加排他鎖前必須先取得該表的IX鎖。
InnoDB行鎖模式兼容列表:
注意:
當一個事務請求的鎖模式與當前的鎖兼容,InnoDB就將請求的鎖授予該事務;反之若是請求不兼容,則該事務就等待鎖釋放。
意向鎖是InnoDB自動加的,不須要用戶干預。
對於insert、update、delete,InnoDB會自動給涉及的數據加排他鎖(X);對於通常的Select語句,InnoDB不會加任何鎖,事務能夠經過如下語句給顯示加共享鎖或排他鎖。
共享鎖:select * from table_name where .....lock in share mode
排他鎖:select * from table_name where .....for update
加入共享鎖的例子:
利用select ....for update加入排他鎖
InnoDB行鎖是經過給索引項加鎖實現的,若是沒有索引,InnoDB會經過隱藏的聚簇索引來對記錄加鎖。
也就是說:若是不經過索引條件檢索數據,那麼InnoDB將對錶中全部數據加鎖,實際效果跟表鎖同樣。
行鎖分爲三種情形:
Record lock :對索引項加鎖,即鎖定一條記錄。
Gap lock:對索引項之間的‘間隙’、對第一條記錄前的間隙或最後一條記錄後的間隙加鎖,即鎖定一個範圍的記錄,不包含記錄自己
Next-key Lock:鎖定一個範圍的記錄幷包含記錄自己(上面二者的結合)。
注意:InnoDB默認級別是repeatable-read級別,因此下面說的都是在RR級別中的。
以前一直搞不懂Gap Lock和Next-key Lock的區別,直到在網上看到一句話豁然開朗,但願對各位有幫助。
Next-Key Lock是行鎖與間隙鎖的組合,這樣,當InnoDB掃描索引記錄的時候,會首先對選中的索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。若是一個間隙被事務T1加了鎖,其它事務是不能在這個間隙插入記錄的。
乾巴巴的說沒意思,咱們來看看具體實例:
假設咱們有一張表:
| id | age |
| 1 | 3 |
| 2 | 6 |
| 3 | 9 |
表結構以下:
CREATE TABLE test
(
id
int(11) NOT NULL AUTO_INCREMENT,
age
int(11) DEFAULT NULL,
PRIMARY KEY (id
),
KEY keyname
(age
)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;
這樣咱們age段的索引就分爲
(negative infinity, 3],
(3,6],
(6,9],
(9,positive infinity);
咱們來看一下幾種狀況:
一、當事務A執行如下語句:
mysql> select * from fenye where age=6for update ;
不只使用行鎖鎖住了相應的數據行,同時也在兩邊的區間,(5,6]和(6,9] 都加入了gap鎖。
這樣事務B就沒法在這個兩個區間insert進新數據,可是事務B能夠在兩個區間外的區間插入數據。
二、當事務A執行
select * from fenye where age=7 for update ;
那麼就會給(6,9]這個區間加鎖,別的事務沒法在此區間插入或更新數據。
三、若是查詢的數據再也不範圍內,
好比事務A執行 select * from fenye where age=100 for update ;
那麼加鎖區間就是(9,positive infinity)。
小結:
行鎖防止別的事務修改或刪除,GAP鎖防止別的事務新增,行鎖和GAP鎖結合造成的的Next-Key鎖共同解決了RR級別在寫數據時的幻讀問題。
InnoDB在絕大部分狀況會使用行級鎖,由於事務和行鎖每每是咱們選擇InnoDB的緣由,可是有些狀況咱們也考慮使用表級鎖。
一、當事務須要更新大部分數據時,表又比較大,若是使用默認的行鎖,不只效率低,並且還容易形成其餘事務長時間等待和鎖衝突。
二、事務比較複雜,極可能引發死鎖致使回滾。
咱們說過MyISAM中是不會產生死鎖的,由於MyISAM老是一次性得到所需的所有鎖,要麼所有知足,要麼所有等待。而在InnoDB中,鎖是逐步得到的,就形成了死鎖的可能。
在上面的例子中咱們能夠看到,當兩個事務都須要得到對方持有的鎖纔可以繼續完成事務,致使雙方都在等待,產生死鎖。
發生死鎖後,InnoDB通常均可以檢測到,並使一個事務釋放鎖回退,另外一個獲取鎖完成事務。
有多種方法能夠避免死鎖,這裏只介紹常見的三種:
一、若是不一樣程序會併發存取多個表,儘可能約定以相同的順序訪問表,能夠大大下降死鎖機會。
二、在同一個事務中,儘量作到一次鎖定所須要的全部資源,減小死鎖產生機率;
三、對於很是容易產生死鎖的業務部分,能夠嘗試使用升級鎖定顆粒度,經過表級鎖定來減小死鎖產生的機率;