Mysql的鎖機制也分爲三大類:
page Lock頁級鎖 、 table LOCK 表鎖 、行鎖
page Lock頁級鎖:NDB事務引擎。
首先查看當前的鎖狀態--
mysql>show status like ‘table%’;
+--------------------------------- +-------+
| Variable_name | Value |
+---------------------------------+--------+
| Table_locks_immediate | 2979 |
| Table_locks_waited | 0 |
+---------------------------------+--------+
如果table_locks_waited的值過高的話,就會導致表鎖爭搶嚴重,應該做出調整。
Myisam引擎的表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)
對MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
對MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;MyISAM表的讀操作與寫操作之間,以及寫操作之間是串行的.
Demo for Lock:(表獨佔寫鎖(Table Write Lock))
lock table table_name write;
此時對table_name這張表加了表獨佔寫鎖(Table Write Lock),
導致其他session無法訪問table_name中的數據信息,就是read操作也無法執行。
當一個線程獲得對一個表的寫鎖後,只有持有鎖的線程可以對錶進行更新操作。其他線程的讀、寫操作都會等待,直到鎖被釋放爲止。
mysql> lock table world.city write;
Query OK, 0 rows affected (0.00 sec)
session2:此時的狀態
mysql>use world
Database changed
mysql>select count(*) from city;
處於等待。
直到session1執行,unlock tables;
session2:
mysql>select count(*) from city;
+----------+
| count(*) |
+----------+
| 4081 |
+----------+
1 row in set (51.65 sec)
Myisam 表在做select查詢的時候都會隱式的加一個讀鎖給相關的表,只能讀不能寫。。
MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,
在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,
這個過程並不需要用戶干預,因此,用戶一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖。
給MyISAM表顯示加鎖,一般是爲了在一定程度模擬事務操作,實現對某一時間點多個表的一致性讀取
Lock tables orders read local, order_detail read local;
Select count(clause) from orders;
Select sum(clause) from order_detail;
Unlock tables;
上面的例子在LOCK TABLES時加了“local”選項,其作用就是在滿足MyISAM表併發插入條件的情況下,
允許其他用戶在表尾併發插入記錄,有關MyISAM表的併發插入問題,在後面的章節中還會進一步介紹。
mysql> lock table city read;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from country;
ERROR 1100 (HY000): Table 'country' was not locked with LOCK TABLES
myisam表在加鎖以後只能訪問這些已經顯示加了鎖的表。
mysql> insert into city (ID) values(98876555)
-> ;
ERROR 1099 (HY000): Table 'city' was locked with a READ lock and can't be updated
myisam表加了讀鎖,就只能執行讀操作,不能更新表數據,
一個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 |
MyISAM存儲引擎有一個系統變量concurrent_insert,專門用以控制其併發插入的行爲,其值分別可以爲0、1或2。
當concurrent_insert設置爲0時,不允許併發插入。
當concurrent_insert設置爲1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),
MyISAM允許在一個進程讀表的同時,另一個進程從表尾插入記錄。這也是MySQL的默認設置。
當concurrent_insert設置爲2時,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。
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語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。
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語句做一定的“分解”,使每一步查詢都能在較短時間完成,從而減少鎖衝突。如果複雜查詢不可避免,應儘量安排在數據庫空閒時段執行,比如一些定期統計可以安排在夜間執行。