鎖是計算機協調多個進程或線程併發訪問某一資源的機制。mysql
MySQL 兩種鎖特性概括 :web
MySQL 不一樣的存儲引擎支持不一樣的鎖機制。
myisam 和 memory 存儲引擎採用的是 表級鎖;
innodb 存儲引擎既支持行級鎖,也支持表級鎖,但默認狀況下采用行級鎖。 sql
表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用,如 web 應用;
而行級鎖則更適合於有大量按索引條件併發更新少許不一樣數據,同時又有併發查詢的應用。數據庫
能夠經過檢查 table_locks_waited 和 table_locks_immediate 狀態變量來分析系統上的表鎖定爭奪:編程
MySQL [sakila]> show status like 'table_locks%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Table_locks_immediate | 100 | | Table_locks_waited | 0 | +-----------------------+-------+
若是 table_locks_waited 的值比較高,則說明存在着較嚴重的表級鎖爭用狀況。安全
MySQL 的表級鎖有兩種模式,表共享讀鎖(table read lock)和表獨佔寫鎖(table write lock)。 session
對 myisam 表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;對 myisam 表的寫操做,則會阻塞其餘用戶對同一表的讀和寫操做;myisam 表的讀操做和寫操做之間,以及寫操做之間時串行的。
併發
當一個線程得到對一個表的寫鎖戶,只有持有鎖的線程能夠對錶進行更新操做,其餘線程的讀、寫操做都會等待,直到鎖被釋放。性能
myisam 在執行查詢語句(select)前,會自動給涉及的全部表加讀鎖,在執行更新操做(update、delete、insert等)前,會自動給涉及的表加寫鎖,這個過程並不須要直接用 lock table 命令給 myisam 表顯示加鎖。給 myisam 表顯式加鎖,通常是爲了在必定程度模擬事務操做
myisam 在自動加鎖的狀況下,老是一次得到 sql 語句所須要的所有鎖,因此顯示鎖表的時候,必須同時取得全部涉及表的鎖,這也正是 myisam 表不會出現死鎖(deadlock)的緣由。 測試
注意:在使用 lock tables 時,不只須要一次鎖定用到的全部表,並且,同一個表在 sql 語句中出現多少次,就要經過與 sql 語句中相同的別名鎖定多少次,不然會報錯。
myisam 表的讀和寫是串行的,但這是就整體而言的。在必定條件下,myisam 表也支持查詢和插入操做的併發進行。
myisam 存儲引擎有一個系統變量 concurrent_insert , 專門用以控制其併發插入的行爲,其值分別能夠爲0,1,2。
myisam 存儲引擎的讀鎖和寫鎖是互斥的,讀寫操做時串行的。當一個進程請求某個 myisam 表的讀鎖,同時另外一個進程也請求同一表的寫鎖時,寫進程會先得到鎖。不只如此,即便讀請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀鎖請求以前,這是由於 mysql 認爲寫請求通常比讀請求重要。這也正是 myisam 表不太適合有大量更新操做和查詢操做應用
的緣由,由於,大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞。
經過一些參數設置能夠調節 MySQL 的默認調度行爲:
上述方式都是要麼更新優先,要麼查詢優先,MySQL 也提供了一種折中的辦法調節讀寫衝突:
給系統參數 max_write_lock_count 設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL 就暫時將寫請求的優先級下降,給讀進程必定得到鎖的機會。
innodb 與 myisam 的最大不一樣有兩點,一是支持事務(transaction),二十採用了行級鎖。
相對於串行處理來講,併發事務處理能力大大增長數據庫資源的利用率,提升數據庫系統事務吞吐量,從而能夠支持更多的用戶,但併發事務處理也會帶來一些問題:
當兩個或多個事務選擇同一行,而後基於最初選定的值更新該行時,因爲每一個事務都不知道其餘事務的存在,就會發生丟失更新問題,最後的更新覆蓋了由其餘事務所作的更新。
一個事務正在對一條記錄作修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此做進一步的處理,就會產生未提交的數據依賴關係。
一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了改變或某些記錄已經被刪除了!這種現象就是「不可重複讀」。
一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象稱爲「幻讀」。
併發事務處理帶來的問題中,「更新丟失」,一般是能夠避免的,須要應用程序對要更新的數據加必要的鎖來解決。
「髒讀」,「不可重複讀」和「幻讀」, 其實都是數據庫讀一致性問題,必須由數據庫提供必定的事務隔離機制來解決。
數據庫實現事務隔離的方式,基本能夠分爲兩種:
數據庫的事務隔離越嚴格,併發反作用越小,但付出的代價也就越大,由於事務隔離實質上就是使事務在必定程度上「串行化」進行,這顯然與「併發」是矛盾的。爲了解決「隔離」與「併發」的矛盾,ISO/ANSI SQL92 定義了 4 個事務隔離級別,MySQL 實現了這四種級別,應用能夠根據本身的業務邏輯要求,選擇合適的隔離級別來平衡「隔離」與「併發」的矛盾。
能夠經過檢查 innodb_row_lock 狀態變量來分析系統上的行鎖的爭奪狀況:
MySQL [sakila]> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+ 5 rows in set (0.00 sec)
若是發現鎖爭用比較嚴重,如 Innodb_row_lock_waits 和 Innodb_row_lock_time_avg 的值比較高,能夠經過查詢 information_schema 數據庫中相關的表來查看鎖狀況,或者經過設置 innodb monitors 來進一步觀察。
MySQL [sakila]> use information_schema Database changed MySQL [information_schema]> select * from innodb_locks \G;
MySQL [sakila]> create table innodb_monitor (a int) engine=innodb; Query OK, 0 rows affected (0.05 sec) show engine innodb status \G;
Innodb 實現了兩種類型的行鎖:
另外,爲了容許行鎖和表鎖共存,事項多粒度鎖機制,innodb 還有兩種內部使用的意向鎖,這兩種意向鎖都是表鎖:
若是一個事務請求的鎖模式與當前的鎖兼容,innodb 就將請求的鎖授予該事務;反之,若是二者不兼容,該事務就要等待鎖釋放。
意向鎖是 innodb 自動加的,不須要用戶干預。對於 update、delete 和 insert 語句,innodb 會自動給涉及數據集加排它鎖(X);對於普通 select 語句,innodb 不會加任何鎖。
事務能夠經過如下語句顯式給記錄集加共享鎖或排它鎖。
用 select... in share mode 得到共享鎖,主要用在須要數據依存關係時來確認某行記錄是否存在,並確保沒有人對這個記錄進行 update 或者 delete 操做。可是若是當前事務也須要對該記錄進行更新操做
,則有可能形成死鎖,對於鎖定行記錄後須要進行更新操做的應用,應該使用 select... for update 方式得到排他鎖。
共享鎖
例子(更新時死鎖)排它鎖
例子innodb 行鎖是經過給索引項加鎖來實現的,若是麼有索引,innodb 將經過隱藏的聚簇索引來對記錄加鎖。innodb 行鎖分爲 3 種情形:
innodb 這種行鎖實現特色意味着:若是不經過索引條件檢索數據,那麼 innodb 將對錶中的全部記錄加鎖,實際效果和表鎖同樣!
在實際應用中,要特別注意 innodb 行鎖的這一特性,不然可能致使大量的鎖衝突,從而影響併發性能。
建立測試表:
MySQL [sakila]> create table tab_no_index (id int, name varchar(10)) engine=innodb; Query OK, 0 rows affected (0.04 sec) MySQL [sakila]> insert into tab_no_index values (1,'1'),(2,'2'),(3,'3'),(4,'4'); Query OK, 4 rows affected (0.01 sec) Records: 4 Duplicates: 0 Warnings: 0
看起來 session_1 只給一行加了排他鎖,但 session_2 在請求其餘行的排他鎖時,卻出現了鎖等待!緣由就是在沒有索引的狀況下,Innodb 會對全部記錄都加鎖。當給其增長一個索引後,innodb 就只鎖定了符合條件的行
建立測試表:
MySQL [sakila]> create table tab_with_index (id int , name varchar(10)) engine = innodb; Query OK, 0 rows affected (0.01 sec) MySQL [sakila]> alter table tab_with_index add index id(id); Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MySQL [sakila]> insert into tab_with_index values (1,'1'),(2,'2'),(3,'3'),(4,'4'); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0
建立測試表,id字段有索引,name字段沒有索引:
MySQL [sakila]> create table tab_with_index (id int , name varchar(10)) engine = innodb; Query OK, 0 rows affected (0.01 sec) MySQL [sakila]> alter table tab_with_index add index id(id); Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MySQL [sakila]> insert into tab_with_index values (1,'1'),(1,'4'); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0
建立測試表,id 字段和 name 字段都有索引:
MySQL [sakila]> create table tab_with_index (id int , name varchar(10),index id (id),index name (name)) engine = innodb; Query OK, 0 rows affected (0.00 sec) MySQL [sakila]> insert into tab_with_index values (1,'1'),(1,'4'),(2,'2'); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0
若是 MySQL 認爲全表掃描效率更高,好比對一些很小的表,它就不會使用索引,這種狀況下 innodb 也會對全部記錄加鎖。
所以,在分析鎖衝突時,別忘了檢查 sql 的執行計劃,以確認是否真正使用了索引。當咱們用範圍條件
而不是相等條件檢索數據,並請求共享或排他鎖時,innodb 會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(gap)」,innodb 也會對這個「間隙」加鎖,這種鎖機制就是所謂的 next-key 鎖。
舉例來講,假如 emp 表中只有 101 條記錄,其 id 的值分別是一、二、...、100、101,下面的 sql:
# 這是一個範圍條件的檢索,innodb 不只會對符合條件的 id 值爲 101 的記錄加鎖,也會對 id 大於 101(這些記錄並不存在)的「間隙」加鎖。 select * from emp where id > 100 for update;
innodb 使用 next-key 鎖的目的,一方面是爲了防止幻讀,以知足相關隔離級別的要求,對於上面的例子,要是不使用間隙鎖,若是其餘事務插入了 id 大於 100 的任何記錄,那麼本事務若是再次執行上述語句,就會發生幻讀;另外一方面,是爲了知足其恢復和複製的須要。
在使用範圍條件檢索並鎖定記錄時,innodb 這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這每每會形成嚴重的鎖等待。所以,在實際開發中,尤爲是併發插入比較多的應用,應該儘可能優化業務邏輯,儘可能使用相等條件來訪問更新數據,避免使用範圍條件。
innodb 除了經過範圍條件加鎖時使用 next-key 鎖外,若是使用相等條件請求給一個不存在的記錄加鎖,innodb 也會使用 next-key 鎖!
MySQL 經過 binlog 記錄執行成功的 insert、update 、delete 等更新數據的 sql 語句,並由此實現 MySQL 數據庫的恢復和主從複製。
MySQL 5.6 支持 3 種 日誌格式,即基於語句的日誌格式 sbl,基於行的日誌格式 rbl 和混合格式。它還支持 4 種複製模式:
對基於語句日誌格式(sbl)的恢復和複製而言,因爲 MySQL 的 binlog 是按照事務提交的前後順序記錄的,所以要正確恢復或複製數據,就必須知足:
在一個事務未提交前,其餘併發事務不能插入知足其鎖定條件的任何記錄,也就是不容許出現幻讀。這已經超過了「可重複讀」隔離級別的要求,其實是要求事務要串行化。這也是許多狀況下,innodb 要用 next-key 鎖的緣由。
對於 innodb 表,在絕大部分狀況下都應該使用行級鎖,由於事務和行鎖每每是咱們選擇 innodb 表的理由,但在個別特殊任務中,也能夠考慮使用表級鎖:
固然,應用中這兩種事務不能太多,不然,就應該考慮使用 myisam 表了。
在 innodb 下,使用表鎖要注意如下兩點:
僅當 autocommit=0、innodb_table_locks=1(默認設置)時,innodb 層才知道 MySQL 加的表鎖,MySQL server 也纔可以感知 innodb 加的行鎖,這種狀況下,innodb 才能自動識別涉及到的鎖
。set autocommit = 0; lock tables ti write, t2 read, ...; [do something with tables t1 and t2 here]; commit; unlock tables;
myisam 表鎖是 deadlock free 的,這是由於 myisam 老是一次獲取所需的所有鎖,要麼所有知足,要麼等待,所以不會出現死鎖。但在 innodb 中,除單個 sql 組成的事務外,鎖是逐步得到的,這就決定了在 innodb 中發生死鎖是可能的。
上面的例子中,兩個事務都須要得到對方持有的排他鎖才能繼續完成事務,這種循環鎖等待就是典型的死鎖。
發生死鎖後,innodb 通常都能自動檢測到,並使一個事務釋放鎖回退,另外一個事務得到鎖,繼續完成事務。但在涉及外部鎖或表鎖的狀況下,innodb 並不能徹底自動檢測到死鎖,只須要經過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決。須要說明的是,這個參數並非只用來解決死鎖問題,在併發訪問比較高的狀況下,若是大量事務因沒法當即得到所需的鎖而掛起,會佔用大量計算機資源,形成嚴重的性能問題,甚至拖垮數據庫。
一般來講,死鎖都是應用設計的問題,經過調整業務流程,數據庫對象設計、事務大小、以及訪問數據庫的 sql 語句,絕大部分死鎖均可以免。
幾種避免死鎖的方法:
下面的例子中,因爲兩個 session 訪問兩個表的順序不一樣,發生死鎖的機會就很是高!但若是以相同的順序來訪問,死鎖就能夠避免。
若是 session_2 以相同的順序執行 sql 語句,會形成鎖等待,但不會死鎖。
對於這種狀況,能夠直接作插入操做,而後再捕獲主鍵重異常,或者在遇到主鍵重錯誤時,老是執行 rollback 釋放得到的排他鎖
儘管經過上面介紹的設計和 sql 優化等措施,能夠大大減小死鎖,但死鎖很難徹底避免。所以,在程序設計中老是捕獲並處理死鎖異常是一個很好的編程習慣
若是出現死鎖,能夠用 show innodb status 命令來肯定最後一個死鎖產生的緣由。返回結果中包括死鎖相關事務的詳細信息,如引起死鎖的 sql 語句,事務已經得到的鎖,正在等待什麼鎖,以及被回滾的事務等。能夠據此分析產生死鎖的緣由。