1. 程式人生 > >深入理解SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE

深入理解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)

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

源mysql官方解釋:
    SELECT ... LOCK IN SHARE MODE sets a shared mode lock on the rows read. A shared mode lock enables other sessions to read the rows but not to modify them. The rows read are the latest available, so if they belong to another transaction that has not yet committed, the read blocks until that transaction ends. 
在讀取的行上設定一個共享模式的鎖。這個共享鎖允許其它session讀取資料但不允許修改它。 行讀取的是最新的資料,如果他被其它事務使用中而沒有提交,讀取鎖將被阻塞知道那個事務結束。 
    SELECT ... FOR UPDATE sets an exclusive lock on the rows read. An exclusive lock prevents other sessions from accessing the rows for reading or writing. 
在讀取行上設定一個排他鎖。組織其他session讀取或者寫入行資料

 

轉自:https://blog.csdn.net/cug_jiang126com/article/details/50544728