鎖是計算機協調多個進程或線程併發訪問某一資源的機制,在數據庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用之外,數據也是一種供許多用戶共享的資源。如何保證數據併發訪問的一致性、有效性是全部數據庫必須解決的一個問題,鎖衝突也是影響數據庫併發訪問的一個重要因素。php
相比其餘數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特色是不一樣的存儲引擎支持不一樣的鎖機制。好比,MyISAM和MEMORY存儲引擎採用的是表級鎖(table-level locking);BDB存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認狀況下是採用行級鎖。
MySQL這3種鎖的特性大體概括以下mysql
- 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生所衝突的機率最高,併發度最低。
- 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生所衝突的機率最低,併發度最高。
- 頁面鎖:開銷和加鎖時間介於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度通常。
從上述特色可見,很難籠統地說哪一種鎖更好,只能就具體應用的特色來講哪一種鎖更合適!僅從鎖的角度來講,表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少不一樣的數據,同時又有併發查詢的應用,如一些在線事務處理(OPTP)系統。
MyISAM存儲引擎只支持表鎖,這也是MySQL開始幾個版本中惟一支持的鎖類型。隨着應用跟對事物完整性和併發性要求的不斷提升,MySQL纔開始開發基於事務的存儲引擎,後來慢慢出現了支持頁鎖的BDB存儲引擎和支持行鎖的InnoDB存儲引擎。可是MyISAM的表鎖依然是使用最爲普遍的鎖類型。sql
mysql> show status like 'table%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Table_locks_immediate | 118 | | Table_locks_waited | 0 | | Table_open_cache_hits | 5 | | Table_open_cache_misses | 3 | | Table_open_cache_overflows | 0 | +----------------------------+-------+ 5 rows in set (0.00 sec)
若是Table_locks_waited的值比較高,則說明存在着較嚴重的表級鎖爭用狀況。數據庫
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。鎖模式的兼容性以下表所示。併發
當前模式\是否兼容\請求鎖模式 | None | 讀鎖 | 寫鎖 |
---|---|---|---|
讀鎖 | 是 | 是 | 否 |
寫鎖 | 是 | 否 | 否 |
可見,對MyISAM表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;對MyISAM表的寫操做,則會阻塞其餘用戶對同一表的度和寫操做;MyISAM表的讀寫操做之間,以及寫操做之間是串行的。spa
MyISAM在執行查詢語句(select)前,會自動給涉及的全部表加讀鎖,在執行更新操做(update、delete、insert等)前,會自動給涉及的表加寫鎖,這個過程並不須要用戶干預,所以,用戶通常不須要直接用LOCK TABLE命令給MyISAM表顯式加鎖。
給MyISAM表顯式加鎖,通常是爲了在必定程度模擬事務操做,實現對某一時間點多個表的一致性讀取。例如,有個訂單表orders,其中記錄有個訂單的總金額total,同時還有一個訂單明細表order_detail,其中記錄有個訂單每一產品的金額小計subtotal,假設須要查找這兩個表的額金額合計是否相符,可能就須要執行以下兩條SQL語句:線程
select sum(total) from orders; select sum(subtotal) from order_detail;
這是,若是不先給兩個表加鎖,就可能產生錯誤的結果,由於地一條語句執行過程當中,order_detail表可能已經發生了改變,所以,正確的方法應該是:code
Lock tables orders read local, order_detail read local; select sum(total) from orders; select sum(subtotal) from order_detail; Unlock tables;
- 以上的例子在LOCK TABLES時加了「local」選項,其做用就是在知足MyISAM表併發插入條件的狀況下,容許其餘用戶在表尾併發插入記錄;
- 在用LOCK TABLES給表顯式加表鎖時,必須同時取得全部涉及表的鎖,而且MySQL不支持鎖升級,也就是說,在執行LOCK TABLES後,只能訪問顯式加鎖的這些表,不能訪問未加鎖的表;同時,若是加的是讀鎖,那麼只能執行查詢操做,而不能執行更新操做。在自動加鎖的狀況下也是如此,MyISAM老是一次得到SQL語句所須要的所有鎖,這也正是MyISAM表不會出現死鎖(Deadlock Free)的緣由。
前面提到的MyISAM表的讀和寫是串行的,但這是就整體而言的,在必定條件下,MyISAM表也支持查詢和插入操做的併發進行。
MyISAM存儲引擎有一個系統變量concurrent_insert,專門用以控制其併發插入的行爲,其值分別能夠爲0、1或2.blog
- 當concurrent_insert設置爲0時,不容許併發插入。
- 當concurrent_insert設置爲1時,若是MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM容許在一個進程讀表的同時,另外一個進程從表尾插入記錄,這也是MyISAM的默認設置。
- 當concurrent_insert設置爲2時,不管MyISAM表中有沒有空洞,都容許在表尾併發插入記錄。
因爲MyISAM存儲引擎的讀鎖和寫鎖是互斥的,讀寫操做是串行的。那麼一個進程該請求某個MyISAM表的讀鎖,同時另外一個進程也請求同一表的寫鎖,MySQL如何處理呢?答案是寫進程先得到鎖。不只如此,即便讀請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀鎖以前!這是由於MySQL認爲寫請求通常比讀請求要重要。這也正是MyISAM表不大適合於有大量更新操做和查詢操做應用的緣由,由於,大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞。這種狀況有可能就會變得很是糟糕!幸虧能夠經過一些設置來調節MyISAM的調度行爲。索引
- 經過指定啓動參數low-priority-updates,使MyISAM引擎默認給予讀請求以優先的權利。
- 經過執行命令SET LOW_PRIORITY_UPDATES=1,是該鏈接發出的更新請求優先級下降。
- 經過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,下降該語句的優先級。
雖然以上3種方法都是要麼更新優先,要麼查詢優先的方法,但仍是能夠用其來解決查詢相對重要的應用(如用戶登陸系統)中讀鎖等待嚴重的問題。
另外,MySQL也提供了一種折中的方法來調節讀寫衝突,即給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個以後,MySQL就暫時將寫請求的優先級下降,給讀進程必定得到鎖的機會。
以上說明了寫優先調度機制帶來的問題和解決辦法,這裏還要強調一點:一些須要長時間運行的查詢操做,也會使寫進程「餓死」!所以,應用中應儘可能避免出現長時間運行的查詢操做,不要總想用一條select語句來解決問題,由於這種看似巧妙的SQL語句,每每比較複雜,執行時間較長,在可能的狀況下能夠經過使用中間表等措施對SQL語句作必定的「分解」,是每一步查詢都能在較短期完成,從而減小鎖衝突。若是複雜查詢不可避免,應儘可能安排在數據庫空閒時段執行,好比一些按期統計能夠安排在夜間執行。