MyISAM表鎖

MyISAM存儲引擎只支持表鎖,這也是MySQL開始幾個版本中惟一支持的鎖類型。隨着應用對事務完整性和併發性 要求的不斷提升,MySQL纔開始開發基於事務的存儲引擎,後來慢慢出現了支持頁鎖的BDB存儲引擎和支持行鎖的InnoDB存儲引擎(實際InnoDB 是單獨的一個公司,如今已經被Oracle公司收購)。可是MyISAM的表鎖依然是使用最爲普遍的鎖類型。本節將詳細介紹MyISAM表鎖的使用。

 

查詢表級鎖爭用狀況

 

能夠經過檢查table_locks_waited和table_locks_immediate狀態變量來分析系統上的表鎖定爭奪:

 


  1. mysql> show status like 'table%';
  2. +-----------------------+-------+
  3. | Variable_name         | Value |
  4. +-----------------------+-------+
  5. | Table_locks_immediate | 2979  |
  6. | Table_locks_waited    | 0     |
  7. +-----------------------+-------+
  8. 2 rows in set (0.00 sec))
複製代碼

 

若是Table_locks_waited的值比較高,則說明存在着較嚴重的表級鎖爭用狀況。

 

MySQL表級鎖的鎖模式

 

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

 

表20-1                                            MySQL中的表鎖兼容性               
請求鎖模式
         是否兼容
當前鎖模式
None
讀鎖
寫鎖
讀鎖
寫鎖

可見,對MyISAM表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;對MyISAM表的寫操做,則會阻塞其餘用戶對同一表的 讀和寫操做;MyISAM表的讀操做與寫操做之間,以及寫操做之間是串行的!根據如表20-2所示的例子能夠知道,當一個線程得到對一個表的寫鎖後,只有 持有鎖的線程能夠對錶進行更新操做。其餘線程的讀、寫操做都會等待,直到鎖被釋放爲止。

 

表20-2                          MyISAM存儲引擎的寫阻塞讀例子
session_1

session_2

得到表film_text的WRITE鎖定
mysql> lock table film_text write;
Query OK, 0 rows affected (0.00 sec)


當前session對鎖定表的查詢、更新、插入操做均可以執行:
mysql> select film_id,title from film_text where film_id = 1001;
+---------+-------------+
| film_id | title       |
+---------+-------------+
| 1001    | Update Test |
+---------+-------------+
1 row in set (0.00 sec)
mysql> insert into film_text (film_id,title) values(1003,'Test');
Query OK, 1 row affected (0.00 sec)
mysql> update film_text set title = 'Test' where film_id = 1001;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

其餘session對鎖定表的查詢被阻塞,須要等待鎖被釋放:
mysql> select film_id,title from film_text where film_id = 1001;
等待

釋放鎖:
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

等待


Session2得到鎖,查詢返回:
mysql> select film_id,title from film_text where film_id = 1001;
+---------+-------+
| film_id | title |
+---------+-------+
| 1001    | Test  |
+---------+-------+
1 row in set (57.59 sec)

 

如何加表鎖

 

MyISAM在執行查詢語句(SELECT)前,會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、 DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不須要用戶干預,所以,用戶通常不須要直接用LOCK TABLE命令給MyISAM表顯式加鎖。在本書的示例中,顯式加鎖基本上都是爲了方便而已,並不是必須如此。

 

給MyISAM表顯示加鎖,通常是爲了在必定程度模擬事務操做,實現對某一時間點多個表的一致性讀取。例如,有一個 訂單表orders,其中記錄有各訂單的總金額total,同時還有一個訂單明細表order_detail,其中記錄有各訂單每一產品的金額小計 subtotal,假設咱們須要檢查這兩個表的金額合計是否相符,可能就須要執行以下兩條SQL:

 


  1. Select sum(total) from orders;
  2. Select sum(subtotal) from order_detail;
複製代碼

 

這時,若是不先給兩個表加鎖,就可能產生錯誤的結果,由於第一條語句執行過程當中,order_detail表可能已經發生了改變。所以,正確的方法應該是:

  1. Lock tables orders read local, order_detail read local;
  2. Select sum(total) from orders;
  3. Select sum(subtotal) from order_detail;
  4. 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能夠查詢表中的記錄,但更新就會出現鎖等待。

 

表20-3                     MyISAM存儲引擎的讀阻塞寫例子
session_1
session_2

得到表film_text的READ鎖定
mysql> lock table film_text read;
Query OK, 0 rows affected (0.00 sec)


當前session能夠查詢該表記錄
mysql> select film_id,title from film_text where film_id = 1001;
+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1001    | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)

其餘session也能夠查詢該表的記錄
mysql> select film_id,title from film_text where film_id = 1001;
+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1001    | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)

當前session不能查詢沒有鎖定的表
mysql> select film_id,title from film where film_id = 1001;
ERROR 1100 (HY000): Table 'film' was not locked with LOCK TABLES

其餘session能夠查詢或者更新未鎖定的表
mysql> select film_id,title from film where film_id = 1001;
+---------+---------------+
| film_id | title         |
+---------+---------------+
| 1001    | update record |
+---------+---------------+
1 row in set (0.00 sec)
mysql> update film set title = 'Test' where film_id = 1001;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

當前session中插入或者更新鎖定的表都會提示錯誤:
mysql> insert into film_text (film_id,title) values(1002,'Test');
ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated
mysql> update film_text set title = 'Test' where film_id = 1001;
ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

其餘session更新鎖定表會等待得到鎖:
mysql> update film_text set title = 'Test' where film_id = 1001;
等待

釋放鎖
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

等待


Session得到鎖,更新操做完成:
mysql> update film_text set title = 'Test' where film_id = 1001;
Query OK, 1 row affected (1 min 0.71 sec)
Rows matched: 1  Changed: 1  Warnings: 0

 

當使用LOCK TABLES時,不只須要一次鎖定用到的全部表,並且,同一個表在SQL語句中出現多少次,就要經過與SQL語句中相同的別名鎖定多少次,不然也會出錯!舉例說明以下。

 

(1)對actor表得到讀鎖:

  1. mysql> lock table actor read;
  2. Query OK, 0 rows affected (0.00 sec)
複製代碼

 

(2)可是經過別名訪問會提示錯誤:

  1. 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;
  2. ERROR 1100 (HY000): Table 'a' was not locked with LOCK TABLES
複製代碼

 

(3)須要對別名分別鎖定:

  1. mysql> lock table actor as a read,actor as b read;
  2. Query OK, 0 rows affected (0.00 sec)
複製代碼

 

(4)按照別名的查詢能夠正確執行:

  1. 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;
  2. +------------+-----------+------------+-----------+
  3. | first_name | last_name | first_name | last_name |
  4. +------------+-----------+------------+-----------+
  5. | Lisa       | Tom       | LISA       | MONROE    |
  6. +------------+-----------+------------+-----------+
  7. 1 row in set (0.00 sec)
複製代碼


併發插入(Concurrent Inserts) mysql

上文提到過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),雖然不能對錶進行刪除和更新操做,但卻能夠對該 表進行併發插入操做,這裏假設該表中間不存在空洞。

 

表20-4              MyISAM存儲引擎的讀寫(INSERT)併發例子
session_1

session_2

得到表film_text的READ LOCAL鎖定
mysql> lock table film_text read local;
Query OK, 0 rows affected (0.00 sec)


當前session不能對鎖定表進行更新或者插入操做:
mysql> insert into film_text (film_id,title) values(1002,'Test');
ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated
mysql> update film_text set title = 'Test' where film_id = 1001;
ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

其餘session能夠進行插入操做,可是更新會等待:
mysql> insert into film_text (film_id,title) values(1002,'Test');
Query OK, 1 row affected (0.00 sec)
mysql> update film_text set title = 'Update Test' where film_id = 1001;
等待

當前session不能訪問其餘session插入的記錄:
mysql> select film_id,title from film_text where film_id = 1002;
Empty set (0.00 sec)


釋放鎖:
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

等待

當前session解鎖後能夠得到其餘session插入的記錄:
mysql> select film_id,title from film_text where film_id = 1002;
+---------+-------+
| film_id | title |
+---------+-------+
| 1002    | Test  |
+---------+-------+
1 row in set (0.00 sec)

Session2得到鎖,更新操做完成:
mysql> update film_text set title = 'Update Test' where film_id = 1001;
Query OK, 1 row affected (1 min 17.75 sec)
Rows matched: 1  Changed: 1  Warnings: 0

 

能夠利用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語句作必定的「分解」,使每一步查詢都能在較短期完成,從而減小鎖衝突。若是複雜查詢不可避免,應儘可能安排在數據庫空閒時段執 行,好比一些按期統計能夠安排在夜間執行。
相關文章
相關標籤/搜索