深刻理解SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE

概念和區別

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)html

經過對比,lock in share mode適用於兩張表存在業務關係時的一致性要求,for  update適用於操做同一張表時的一致性要求。mysql

相關文章
相關標籤/搜索