深入理解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時就會引起鎖等待,等待session2)
mysql> update test_jjj set name='jjj1' where name='jjj';
session2(這時session2同樣update,引起鎖等待,等待session1,接著檢測到死鎖,回滾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
備註:可以通過以下三個命令檢視事務的狀態
1:檢視當前的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;殺死事務程式id(就是上面命令的trx_mysql_thread_id列)
2:檢視當前鎖定的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:檢視當前等鎖的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
通過該案例可知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 共享鎖 (lock in share mode),排他鎖 (for update)MySql
- select for update
- 由select for update鎖等待問題引發的深入思考
- sql查詢更新update selectSQL
- 理解select、epoll
- mysql update join,insert select 語法MySql
- SQLite語句(二):INSERT DELETE UPDATE SELECTSQLitedelete
- java-Mybatis XML 對映器(select,insert, update 和 delete)JavaMyBatisXMLdelete
- select into from 和 insert into select 的用法和區別
- Oracle中select for update ...一些區別Oracle
- DBeaver如何生成select,update,delete,insert語句delete
- select * 和 select 所有欄位的區別
- MySQL中SELECT+UPDATE併發更新問題MySql
- select 下拉框用 Select select = new Select (element) 方法失敗
- IO多路複用——深入淺出理解select、poll、epoll的實現
- golang當中對select的理解Golang
- 記一次 MySQL select for update 死鎖問題MySql
- Innodb:為什麼lock in share mode在show engine看不到行鎖資訊
- ORACLE LOCK MODE 1.2.3.4.5.6Oracle
- Django筆記十三之select_for_update等選擇和更新等相關操作Django筆記
- Linux select()Linux
- insert into select
- select()APIAPI
- insert into select語句與select into from語句
- mysql 高併發 select update 併發更新問題解決方案MySql
- Laravel5.6 如何列印 SQL?insert/update/select 列印方法總結LaravelSQL
- 聊聊select, poll 和 epoll_waitAI
- SQLSERVER SELECT(zt)SQLServer
- JavaScript select remove()JavaScriptREM
- JavaScript select add()JavaScript
- JavaScript select typeJavaScript
- JavaScript select valueJavaScript
- JavaScript select multipleJavaScript
- SQL__SELECTSQL
- JavaScript select 事件JavaScript事件
- 阿里OSS Select阿里
- mysql select稽核MySql
- Go的SelectGo