9.MYSQL:MyISAM表级锁(共享读锁 独占写锁)串行+并发
數據庫管理系統(DBMS)中的并發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和統一性以及數據庫的統一性。下面舉例說明并發操作帶來的數據不一致性問題:
現有兩處火車票售票點,同時讀取某一趟列車車票數據庫中車票余額為 X。兩處售票點同時賣出一張車票,同時修改余額為 X -1寫回數據庫,這樣就造成了實際賣出兩張火車票而數據庫中的記錄卻只少了一張。 產生這種情況的原因是因為兩個事務讀入同一數據并同時修改,其中一個事務提交的結果破壞了另一個事務提交的結果,導致其數據的修改被丟失,破壞了事務的隔離性。并發控制要解決的就是這類問題。
鎖是計算機協調多個進程或線程并發訪問某一資源的機制。在數據庫中,數據也是一種供許多用戶共享的資源。如何保證數據訪問的一致性、有效性是所有數據庫的一個問題,鎖沖突也是影響數據庫并發訪問性能的一個重要的因素,從這個角度來說,鎖對數據庫而言顯得尤其重要,也更加復雜。
相對于其它的數據庫而言,MYSQL的鎖機制比較簡單的,其最顯著的特點就是不同的搜索引擎具有不同的鎖機制。
MYISAM和MEMORY支持表級鎖;
BDB支持頁級鎖;
INNODB既支持表級鎖,也支持行級鎖,默認的是行級鎖。
可以通過檢查table_locks_waited和table_locks_immediate狀態變量來分析系統上的表鎖定爭奪:
show status like '%table%';
得出的結果如果Table_locks_waited的值比較高,則說明存在著較嚴重的表級鎖爭用情況。
MySQL這三種鎖的特征:開銷、加鎖速度、死鎖、粒度、并發性能
表級鎖:開銷小,加鎖快,不會出現死鎖,鎖定粒度大,發生沖突的概率最高,并發度最低;
行級鎖:開銷大,加鎖慢,會出現死鎖,鎖定粒度小,發生鎖沖突的概率最低,并發度最高;
頁面鎖:開銷介于前兩者之間,會出現死鎖,鎖定粒度介于前兩者之間,并發度一般;
僅從鎖的角度來說:
表級鎖更適合于以查詢為主,只有少量按索引條件更新數據的應用,如web應用;
行級鎖則更適合于有大量按索引條件并發更新少量不同數據,同時僅有并發查詢的應用,如一些在線事務處理(OLTP)系統。
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨占寫鎖(Table Write LocK)。
1、加共享讀鎖
lock table 表名 read;
2、解鎖
unlock tables;
3、表獨占寫鎖
lock table 表名 write;
注意:
1)對MYISAM表的讀操作,不會阻塞其他session的讀請求,但是在加鎖session沒有unlock期間,會阻塞其它session對于該表的寫請求,只有當加鎖session進行unlock之后,數據庫會繼續執行阻塞的寫操作;
2)對MyISAM表的寫操作,則會阻塞其他用戶對同一張表的讀和寫操作;
3)MyISAM表的讀操作與寫的操作之間,以及寫操作之間是串行的!當一個線程獲得對一個表的寫鎖后,只有持有鎖的線程可以對表進行更新操作,其他線程的讀、寫操作都會等待,直到鎖被釋放為止。
注意一點:
1) 如果要使用表的別名,記住:用別名加鎖,才能使用別名存取。
2)一個session對表加共享讀鎖之后,這個會話可以訪問加鎖的表,但是在這個session訪問其他的表就會報錯!
MyISAM表的讀操作與寫的操作之間,以及寫操作之間是串行的!當然也支持并發操作!
lock table 表名 read local;? --->并發
lock table 表名 write local;? --->并發
MyISAM存儲引擎有一個系統變量concurrent_insert ,專門用來控制并發插入的行為,其值可以為0,1,2。默認為1。
1)當concurrent_insert 設置為0時,不允許并發插入!
2)當concurrent_insert設置為1時,如果MYISAM表中之前沒有過刪除操作,則MYISAM表允許一個表讀的時候,另一個表從表尾插入記錄。這也是MYSQL的默認設置。
3)當concurrent_insert設置為2時,不管有沒有刪除操作,都可以表尾并發插入記錄!
MYSQL認為寫請求的優先級高于讀請求!
概念和區別
SELECT ... LOCK IN SHARE MODE走的是IS鎖(意向共享鎖),即在符合條件的rows上都加了共享鎖,這樣的話,其他session可以讀取這些記錄,也可以繼續添加IS鎖,但是無法修改這些記錄直到你這個加鎖的session執行完成(否則直接鎖等待超時)。
SELECT ... FOR UPDATE 走的是IX鎖(意向排它鎖),即在符合條件的rows上都加了排它鎖,其他session也就無法在這些記錄上添加任何的S鎖或X鎖。如果不存在一致性非鎖定讀的話,那么其他session是無法讀取和修改這些記錄的,但是innodb有非鎖定讀(快照讀并不需要加鎖),for update之后并不會阻塞其他session的快照讀取操作,除了select ...lock in share mode和select ... for update這種顯示加鎖的查詢操作。
通過對比,發現for update的加鎖方式無非是比lock in share mode的方式多阻塞了select...lock in share mode的查詢方式,并不會阻塞快照讀。
應用場景
在我看來,SELECT ... LOCK IN SHARE MODE的應用場景適合于兩張表存在關系時的寫操作,拿mysql官方文檔的例子來說,一個表是child表,一個是parent表,假設child表的某一列child_id映射到parent表的c_child_id列,那么從業務角度講,此時我直接insert一條child_id=100記錄到child表是存在風險的,因為剛insert的時候可能在parent表里刪除了這條c_child_id=100的記錄,那么業務數據就存在不一致的風險。正確的方法是再插入時執行select * from parent where c_child_id=100 lock in share mode,鎖定了parent表的這條記錄,然后執行insert into child(child_id) values (100)就ok了。
但是如果是同一張表的應用場景,舉個例子,電商系統中計算一種商品的剩余數量,在產生訂單之前需要確認商品數量>=1,產生訂單之后應該將商品數量減1。
1 select amount from product where product_name='XX';
2 update product set amount=amount-1 where product_name='XX';
顯然1的做法是是有問題,因為如果1查詢出amount為1,但是這時正好其他session也買了該商品并產生了訂單,那么amount就變成了0,那么這時第二步再執行就有問題。
那么采用lock in share mode可行嗎,也是不合理的,因為兩個session同時鎖定該行記錄時,這時兩個session再update時必然會產生死鎖導致事務回滾。以下是操作范例(按時間順序)
session1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_jjj lock in share mode;
+-----+------------+
| id ?| name ??????|
+-----+------------+
| 234 | asdasdy123 |
| 123 | jjj ???????|
+-----+------------+
2 rows in set (0.00 sec)
session2(同樣鎖定了相同的行)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_jjj lock in share mode;
+-----+------------+
| id ?| name ??????|
+-----+------------+
| 234 | asdasdy123 |
| 123 | jjj ???????|
+-----+------------+
2 rows in set (0.00 sec)
session1(這時session1再update時就會引起鎖等待)
mysql> update test_jjj set name='jjj1' where name='jjj';
session2(這時session2同樣update就會檢測到死鎖,回滾session2,注意執行時間不要超過session1的鎖等待超時檢測時間,即不要超過innodb_lock_wait_timeout設置的值)
mysql> update test_jjj set name='jjj1' where name='jjj';
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
session1(此時session1執行完成)
mysql> update test_jjj set name='jjj1' where name='jjj';
Query OK, 1 row affected (29.20 sec)
Rows matched: 1 ?Changed: 1 ?Warnings: 0
通過該案例可知lock in share mode的方式在這個場景中不適用,我們需要使用for ?update的方式直接加X鎖,從而短暫地阻塞session2的select...for update操作;以下是操作范例
session1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_jjj for update;
+-----+------------+
| id ?| name ??????|
+-----+------------+
| 234 | asdasdy123 |
| 123 | jjj1 ??????|
+-----+------------+
2 rows in set (0.00 sec)
session2(此時session2處于鎖等待狀態,得不到結果)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_jjj for update;
session1(這時session1 update之后提交,可完成)
mysql> update test_jjj set name='jjj1' where name='jjj';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 ?Changed: 0 ?Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
session2(session1提交之后session2剛才的查詢結果就出來了,也就可以再次update往下執行了)
mysql> select * from test_jjj for update;
+-----+------------+
| id ?| name ??????|
+-----+------------+
| 234 | asdasdy123 |
| 123 | jjj1 ??????|
+-----+------------+
2 rows in set (37.19 sec)
mysql> select * from test_jjj for update;
+-----+------------+
| id ?| name ??????|
+-----+------------+
| 234 | asdasdy123 |
| 123 | jjj1 ??????|
+-----+------------+
2 rows in set (37.19 sec)
mysql> update test_jjj set name='jjj1' where name='jjj';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 ?Changed: 0 ?Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
通過對比,lock in share mode適用于兩張表存在業務關系時的一致性要求,for ?update適用于操作同一張表時的一致性要求。
?
總結
以上是生活随笔為你收集整理的9.MYSQL:MyISAM表级锁(共享读锁 独占写锁)串行+并发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 23.C语言中#if 0,#if 1,#
- 下一篇: 10.MYSQL:什么是事务?事务的性质