1. 程式人生 > >mysql鎖表機制及相關優化

mysql鎖表機制及相關優化

(該文章為方便自己查閱,也希望對大家有所幫助,轉載於網際網路)
1、 鎖機制
當前MySQL支援 ISAM, MyISAM, MEMORY (HEAP) 型別表的表級鎖,BDB 表支援頁級鎖,InnoDB 表支援行級鎖。
很多時候,可以通過經驗來猜測什麼樣的鎖對應用程式更合適,不過通常很難說一個鎖比別的更好,這全都要依據應用程式來決定,不同的地方可能需要不同的鎖。
想要決定是否需要採用一個支援行級鎖的儲存引擎,就要看看應用程式都要做什麼,其中的查詢、更新語句是怎麼用的。例如,很多的web應用程式大量的做查詢,很少刪除,主要是基於索引的更新,只往特定的表中插入記錄。採用基本的MySQL的MyISAM 表就很合適了。
MySQL中對錶級鎖的儲存引擎來說是釋放死鎖的。避免死鎖可以這樣做到:在任何查詢之前先請求鎖,並且按照請求的順序鎖表。
1)MySQL中用於 WRITE(寫) 的表鎖的實現機制如下:
如果表沒有加鎖,那麼就加一個寫鎖。
否則的話,將請求放到寫鎖佇列中。
2)MySQL中用於 READ(讀) 的表鎖的實現機制如下:
如果表沒有加寫鎖,那麼就加一個讀鎖。
否則的話,將請求放到讀鎖佇列中。
當鎖釋放後,寫鎖佇列中的執行緒可以用這個鎖資源,然後才輪到讀鎖佇列中的執行緒程。
這就是說,如果表裡有很多更新操作的話,那麼 Select 必須等到所有的更新都完成了之後才能開始。
現在版本的MySQL可以通過狀態變數table_locks_waited 和table_locks_immediate 來分析系統中的鎖表爭奪情況:
mysql> show status like 'table%';
+-----------------------+----------+
| Variable_name         | Value    |
+-----------------------+----------+
| Table_locks_immediate | 15184994 |
| Table_locks_waited    | 20108    |
+-----------------------+----------+
2 rows in set (0.00 sec)
在 MySQL 3.23.7(在Windows上是3.23.25)以後,在 MyISAM 表中只要沒有衝突的Insert 操作,就可以無需使用鎖表自由地並行執行Insert 和Select 語句。也就是說,可以在其它客戶端正在讀取MyISAM 表記錄的同時時插入新記錄。如果資料檔案的中間沒有空餘的磁碟塊的話,就不會發生衝突了,因為這種情況下所有的新記錄都會寫在資料檔案的末尾(當在表的中間做刪除或者更新操作時,就可能導致空洞)。當空洞被新資料填充後,並行插入特性就會自動重新被啟用了。
mysql>lock  tables real_table  write, insert_table  write;
mysql> insert  into  real_table  select * from insert_table;
mysql> truncate  table  insert_table;
mysql> unlock  tables; (自己斟酌)
InnoDB 使用行級鎖,BDB 使用頁級鎖。對於 InnoDB 和 BDB 儲存引擎來說,是可能產生死鎖的。這是因為 InnoDB 會自動捕獲行鎖,BDB 會在執行 SQL 語句時捕獲頁鎖的,而不是在事務的開始就這麼做。
行級鎖的優點有: (innodb)
在很多執行緒請求不同記錄時減少衝突鎖。
事務回滾時減少改變資料。
使長時間對單獨的一行記錄加鎖成為可能。
行級鎖的缺點有:
比頁級鎖和表級鎖消耗更多的記憶體。
當在大量表中使用時,比頁級鎖和表級鎖更慢,因為他需要請求更多的鎖資源。
當需要頻繁對大部分資料做GROUP BY 操作或者需要頻繁掃描整個表時,就明顯的比其它鎖更糟糕。
-----------------------------------------------------------------------------------------
使用更高層的鎖的話,就能更方便的支援各種不同的型別應用程式,因為這種鎖的開銷比行級鎖小多了。
表級鎖在下列幾種情況下比頁級鎖和行級鎖更優越:
很多操作都是讀表。
在嚴格條件的索引上讀取和更新,當更新或者刪除可以用單獨的索引來讀取得到時:
Update tbl_name SET column=value Where unique_key_col=key_value;   
Delete FROM tbl_name Where unique_key_col=key_value;   
Select 和 Insert 語句併發的執行,但是隻有很少的 Update 和 Delete 語句。
很多的掃描表和對全表的 GROUP BY 操作,但是沒有任何寫表。
2、 鎖表
為了能有快速的鎖,MySQL除了 InnoDB 和 BDB 這兩種儲存引擎外,所有的都是用表級鎖(而非頁、行、列級鎖)。
對於 InnoDB 和 BDB 表,MySQL只有在指定用 LOCK TABLES 鎖表時才使用表級鎖。在這兩種表中,建議最好不要使用 LOCK TABLES,因為 InnoDB 自動採用行級鎖,BDB 用頁級鎖來保證事務的隔離。
如果資料表很大,那麼在大多數應用中表級鎖會比行級鎖好多了,不過這有一些陷阱。
表級鎖讓很多執行緒可以同時從資料表中讀取資料,但是如果另一個執行緒想要寫資料的話,就必須要先取得排他訪問。正在更新資料時,必須要等到更新完成了,其他執行緒才能訪問這個表。
更新操作通常認為比讀取更重要,因此它的優先順序更高。不過最好要先確認,資料表是否有很高的 Select 操作,而更新操作並非很‘急需’。
表鎖鎖在一個執行緒在等待,因為磁碟空間滿了,但是卻需要有空餘的磁碟空間,這個執行緒才能繼續處理時就有問題了。這種情況下,所有要訪問這個出問題的表的執行緒都會被置為等待狀態,直到有剩餘磁碟空間了。
表鎖在以下設想情況中就不利了:
一個客戶端提交了一個需要長時間執行的 Select 操作。
其他客戶端對同一個表提交了 Update 操作,這個客戶端就要等到 Select 完成了才能開始執行。
其他客戶端也對同一個表提交了 Select 請求。由於 Update 的優先順序高於 Select,所以 Select 就會先等到 Update 完成了之後才開始執行,它也在等待第一個 Select 操作。
下列所述可以減少表鎖帶來的資源爭奪:
讓 Select 速度儘量快,這可能需要建立一些摘要表。
啟動 mysqld 時使用引數 --low-priority-updates。這就會讓更新操作的優先順序低於 Select。這種情況下,在上面的假設中,第二個 Select 就會在 Insert 之前執行了,而且也無需等待第一個Select 了。
可以執行 SET LOW_PRIORITY_UpdateS=1 命令,指定所有的更新操作都放到一個指定的連結中去完成。
用 LOW_PRIORITY 屬性來降低 Insert,Update,Delete 的優先順序。
用 HIGH_PRIORITY 來提高 Select 語句的優先順序。
從MySQL 3.23.7 開始,可以在啟動 mysqld 時指定系統變數 max_write_lock_count 為一個比較低的值,它能強制臨時地提高表的插入數達到一個特定值後的所有 Select 操作的優先順序。它允許在 WRITE 鎖達到一定數量後有 READ 鎖。
當 Insert 和 Select 一起使用出現問題時,可以轉而採用 MyISAM 表,它支援併發的Select 和 Insert 操作。
當在同一個表上同時有插入和刪除操作時,Insert DELAYED 可能會很有用。
當 Select 和 Delete 一起使用出現問題時,Delete 的 LIMIT 引數可能會很有用。
執行 Select 時使用 SQL_BUFFER_RESULT 有助於減短鎖表的持續時間.
以下是MySQL鎖的一些建議:
只要對同一個表沒有大量的更新和查詢操作混在一起,目前的使用者並不是問題。
執行 LOCK TABLES 來提高速度(很多更新操作放在一個鎖之中比沒有鎖的很多更新快多了)。將資料拆分開到多個表中可能也有幫助。
當MySQL碰到由於鎖表引起的速度問題時,將表型別轉換成 InnoDB 或 BDB 可能有助於提高效能。


******************************************************************************************************************************************************************
*********************************************************************************
*********************************************************************************

鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制 。在資料庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,資料也是一種供許多使用者共享的資源。如何保證資料併發訪問的一致性、有效性是所有資料庫必須解決的一個問題,鎖衝突也是影響資料庫併發訪問效能的一個重要因素。 從這個角度來說,鎖對資料庫而言顯得尤其重要,也更加複雜。本章我們著重討論MySQL鎖機制的特點,常見的鎖問題,以及解決MySQL鎖問題的一些方法或建議。

MySQL鎖概述
相對其他資料庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的儲存引擎支援不同的鎖機制。
比如,MyISAM和MEMORY儲存引擎採用的是表級鎖(table-level locking);
BDB儲存引擎採用的是頁面鎖(page-level locking),但也支援表級鎖;
InnoDB儲存引擎既支援行級鎖(row-level locking),也支援表級鎖,但預設情況下是採用行級鎖。
MySQL這3種鎖的特性可大致歸納如下。
     表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
     行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
     頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
從上述特點可見,很難籠統地說哪種鎖更好,只能就具體應用的特點來說哪種鎖更合適!僅從鎖的角度來說:表級鎖更適合於以查詢為主,只有少量按索引條件更新資料的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少量不同資料,同時又有併發查詢的應用,如一些線上事務處理(OLTP)系統。這一點在本書的“開發篇”介紹表型別的選擇時,也曾提到過。下面幾節我們重點介紹MySQL表鎖和 InnoDB行鎖的問題,由於BDB已經被InnoDB取代,即將成為歷史,在此就不做進一步的討論了。





MyISAM表鎖

MyISAM儲存引擎只支援表鎖,這也是MySQL開始幾個版本中唯一支援的鎖型別。隨著應用對事務完整性和併發性要求的不斷提高,MySQL才開始開發基於事務的儲存引擎,後來慢慢出現了支援頁鎖的BDB儲存引擎和支援行鎖的InnoDB儲存引擎(實際 InnoDB是單獨的一個公司,現在已經被Oracle公司收購)。但是MyISAM的表鎖依然是使用最為廣泛的鎖型別。本節將詳細介紹MyISAM表鎖的使用。
可以通過檢查table_locks_waited和table_locks_immediate狀態變數來分析系統上的表鎖定爭奪:
Java程式碼  收藏程式碼

    mysql> show status like 'table%';    
    +-----------------------+-------+    
    | Variable_name         | Value |    
    +-----------------------+-------+    
    | Table_locks_immediate | 2979  |    
    | Table_locks_waited    | 0     |    
    +-----------------------+-------+   
     
    mysql> show status like 'table%'; 
    +-----------------------+-------+ 
    | Variable_name         | Value | 
    +-----------------------+-------+ 
    | Table_locks_immediate | 2979  | 
    | Table_locks_waited    | 0     | 
    +-----------------------+-------+ 


如果Table_locks_waited的值比較高,則說明存在著較嚴重的表級鎖爭用情況。


MySQL表級鎖的鎖模式
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。鎖模式的相容性如表20-1所示。

可見,對MyISAM表的讀操作,不會阻塞其他使用者對同一表的讀請求,但會阻塞對同一表的寫請求;對 MyISAM表的寫操作,則會阻塞其他使用者對同一表的讀和寫操作;MyISAM表的讀操作與寫操作之間,以及寫操作之間是序列的!根據如表20-2所示的例子可以知道,當一個執行緒獲得對一個表的寫鎖後,只有持有鎖的執行緒可以對錶進行更新操作。其他執行緒的讀、寫操作都會等待,直到鎖被釋放為止。




如何加表鎖
MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不需要使用者干預,因此,使用者一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖。在本書的示例中,顯式加鎖基本上都是為了方便而已,並非必須如此。
給MyISAM表顯示加鎖,一般是為了在一定程度模擬事務操作,實現對某一時間點多個表的一致性讀取。例如,有一個訂單表orders,其中記錄有各訂單的總金額total,同時還有一個訂單明細表order_detail,其中記錄有各訂單每一產品的金額小計 subtotal,假設我們需要檢查這兩個表的金額合計是否相符,可能就需要執行如下兩條SQL:
Java程式碼  收藏程式碼

    Select sum(total) from orders;    
    Select sum(subtotal) from order_detail;   
     
    Select sum(total) from orders; 
    Select sum(subtotal) from order_detail; 

這時,如果不先給兩個表加鎖,就可能產生錯誤的結果,因為第一條語句執行過程中,order_detail表可能已經發生了改變。因此,正確的方法應該是:
Java程式碼  收藏程式碼

    Lock tables orders read local, order_detail read local;    
    Select sum(total) from orders;    
    Select sum(subtotal) from order_detail;    
    Unlock tables;   
     
    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表併發插入條件的情況下,允許其他使用者在表尾併發插入記錄,有關MyISAM表的併發插入問題,在後面的章節中還會進一步介紹。
¡  在用LOCK TABLES給表顯式加表鎖時,必須同時取得所有涉及到表的鎖,並且MySQL不支援鎖升級。也就是說,在執行LOCK TABLES後,只能訪問顯式加鎖的這些表,不能訪問未加鎖的表;同時,如果加的是讀鎖,那麼只能執行查詢操作,而不能執行更新操作。其實,在自動加鎖的情況下也基本如此,MyISAM總是一次獲得SQL語句所需要的全部鎖。這也正是MyISAM表不會出現死鎖(Deadlock Free)的原因。
在如表20-3所示的例子中,一個session使用LOCK TABLE命令給表film_text加了讀鎖,這個session可以查詢鎖定表中的記錄,但更新或訪問其他表都會提示錯誤;同時,另外一個session可以查詢表中的記錄,但更新就會出現鎖等待。




當使用LOCK TABLES時,不僅需要一次鎖定用到的所有表,而且,同一個表在SQL語句中出現多少次,就要通過與SQL語句中相同的別名鎖定多少次,否則也會出錯!舉例說明如下。
(1)對actor表獲得讀鎖:
Java程式碼  收藏程式碼

    mysql> lock table actor read;    
    Query OK, 0 rows affected (0.00 sec)   


mysql> lock table actor read;
Query OK, 0 rows affected (0.00 sec)
(2)但是通過別名訪問會提示錯誤:
Java程式碼  收藏程式碼

      
    mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name;    
    ERROR 1100 (HY000): Table 'a' was not locked with LOCK TABLES   
     
    mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name; 
    ERROR 1100 (HY000): Table 'a' was not locked with LOCK TABLES 


(3)需要對別名分別鎖定:
Java程式碼  收藏程式碼

      
    mysql> lock table actor as a read,actor as b read;    
    Query OK, 0 rows affected (0.00 sec)   
     
    mysql> lock table actor as a read,actor as b read; 
    Query OK, 0 rows affected (0.00 sec)  

(4)按照別名的查詢可以正確執行:
Java程式碼  收藏程式碼

      
    mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name;    
    +------------+-----------+------------+-----------+    
    | first_name | last_name | first_name | last_name |    
    +------------+-----------+------------+-----------+    
    | Lisa       | Tom       | LISA       | MONROE    |    
    +------------+-----------+------------+-----------+    
    1 row in set (0.00 sec)   
     
    mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name; 
    +------------+-----------+------------+-----------+ 
    | first_name | last_name | first_name | last_name | 
    +------------+-----------+------------+-----------+ 
    | Lisa       | Tom       | LISA       | MONROE    | 
    +------------+-----------+------------+-----------+ 
    1 row in set (0.00 sec)  


併發插入(Concurrent Inserts)
上文提到過MyISAM表的讀和寫是序列的,但這是就總體而言的。在一定條件下,MyISAM表也支援查詢和插入操作的併發進行。
MyISAM儲存引擎有一個系統變數concurrent_insert,專門用以控制其併發插入的行為,其值分別可以為0、1或2。
        當concurrent_insert設定為0時,不允許併發插入。
        當concurrent_insert設定為1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個程序讀表的同時,另一個程序從表尾插入記錄。這也是MySQL的預設設定。
       當concurrent_insert設定為2時,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。
在如表20-4所示的例子中,session_1獲得了一個表的READ LOCAL鎖,該執行緒可以對錶進行查詢操作,但不能對錶進行更新操作;其他的執行緒(session_2),雖然不能對錶進行刪除和更新操作,但卻可以對該表進行併發插入操作,這裡假設該表中間不存在空洞。



可以利用MyISAM儲存引擎的併發插入特性,來解決應用中對同一表查詢和插入的鎖爭用。例如,將concurrent_insert系統變數設為2,總是允許併發插入;同時,通過定期在系統空閒時段執行 OPTIMIZE TABLE語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。有關OPTIMIZE TABLE語句的詳細介紹,可以參見第18章中“兩個簡單實用的優化方法”一節的內容。
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語句做一定的“分解”,使每一步查詢都能在較短時間完成,從而減少鎖衝突。如果複雜查詢不可避免,應儘量安排在資料庫空閒時段執行,比如一些定期統計可以安排在夜間執行。