MysqL_select for update鎖詳解

OldBoy~發表於2017-12-05

先來舉一個在某些應用場景下會出現資料不一致的例子,當然儲存引擎是InnoDB(至於為什麼,後面再告訴你)。

電商平臺常見的下單場景:

一般商品表(goods)有基本的四個欄位,id(主鍵),goods_name (商品名),goods_status是商品狀態(下架還是在售),goods_stock(庫存)。

使用者在購買商品id為1的商品,雖然一般展示層會篩選出在售狀態的商品,在嚴謹的流程中我們還需要判斷一下是否在售,商品的庫存數量等條件是否正常,來避免出現問題。但併發情況下,可能會出現使用者在購買將要付費的時候,商品管理人員搶先把商品下架,就會出現不一致了。

select goods_status,goods_stock from goods where id=1;  //查出商品狀態和庫存

inset into orders (goods_id,goods_count) values (1,3);      //如果庫存和狀態正常,把購買的商品和數量寫入訂單表

接下來還會有付費減庫存等操作......

這裡商品管理人員突然對商品的狀態或者庫存做了調整

update goods set goods_status = 0 where id =3;  //修改狀態為例//

在上面的場景中,商品資訊從查詢出來到修改,中間有一個處理訂單的過程。那麼可以使用悲觀鎖來解決此問題。

使用悲觀鎖的原理就是,當我們在查詢出goods資訊後就把當前的資料鎖定,直到我們修改完畢後再解鎖。那麼在這個過程中,因為goods被鎖定了,就不會出現有第三者來對其進行修改了。

要注意的是,使用悲觀鎖,必須關閉Mysql的自動提交機制:http://www.cnblogs.com/wt645631686/p/7986696.html

//0.開始事務
begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品資訊
select goods_status from goods where id=1 for update;
//2.根據商品資訊生成訂單
insert into orders (goods_id,goods_count) values (3,5);
//3.修改商品status為2
update goods set status=2;
//4.提交事務
commit;/commit work;

拿上面的例項來說,當我執行select status from goods where id=3 for update;後。我在另外的事務中如果再次執行select status from goods where id=3 for update;則第二個事務會一直等待第一個事務的提交,此時第二個查詢處於阻塞的狀態,但是如果我是在第二個事務中執行select status from goods where id=3;則能正常查詢出資料,不會受第一個事務的影響。

舉例說明
資料庫表goods,包括id,status,name三個欄位,id為主鍵,資料庫中記錄如下;

mysql> select * from goods; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1     | 道具 | 
| 2  | 1     | 裝備 | 
+----+--------+------+ 

注:為了測試資料庫鎖,我使用兩個console來模擬不同的事務操作,分別用console1、console2來表示。

例1: (明確指定主鍵,並且有此資料,row lock)
console1:查詢出結果,但是把該條資料鎖定了

mysql> select * from goods where id=1 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1     | 道具 | 
+----+--------+------+ 

console2:查詢被阻塞

mysql> select * from goods where id=1 for update; 

console2:如果console1長時間未提交,則會報錯

mysql> select * from goods where id=1 for update; 
ERROR 1205 : Lock wait timeout exceeded; try restarting transaction 

例2: (明確指定主鍵,若查無此資料,無lock)
console1:查詢結果為空

mysql> select * from goods where id=3 for update; 
Empty set 

console2:查詢結果為空,查詢無阻塞,說明console1沒有對資料執行鎖定

mysql> select * from goods where id=3 for update; 
Empty set 

例3: (無主鍵,table lock)

console1:查詢name=道具 的資料,查詢正常

mysql> select * from goods where name='道具' for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1    | 道具 | 
+----+--------+------+ 

console2:查詢name=裝備 的資料,查詢阻塞,說明console1把表給鎖住了

mysql> select * from goods where name='裝備' for update; 

console2:若console1長時間未提交,則查詢返回為空

mysql> select * from goods where name='裝備' for update; 
Query OK, -1 rows affected 

例4: (主鍵不明確,table lock)
console1:查詢正常

mysql> begin; 
Query OK, 0 rows affected 
mysql> select * from goods where id>0 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1     | 道具 | 
| 2  | 1     | 裝備 | 
+----+--------+------+ 

console2:查詢被阻塞,說明console1把表給鎖住了

mysql> select * from t_goods where id>1 for update; 

例5: (主鍵不明確,table lock)

console1:

mysql> begin; 
Query OK, 0 rows affected 

mysql> select * from t_goods where id<>1 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 2  | 1      | 裝備 | 
+----+--------+------+ 

console2:查詢被阻塞,說明console1把表給鎖住了

mysql> select * from t_goods where id<>2 for update; 

console1:提交事務

mysql> commit; 
Query OK, 0 rows affected 

console2:console1事務提交後,console2查詢結果正常

mysql> select * from t_goods where id<>2 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1      | 道具 | 
+----+--------+------+ 

 

以上就是關於資料庫主鍵對MySQL鎖級別的影響例項,需要注意的是,除了主鍵外,使用索引也會影響資料庫的鎖定級別
舉例:
我們修改goods表,給status欄位建立一個索引
修改id為2的資料的status為2,此時表中資料為:

mysql> select * from goods; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1      | 道具 | 
| 2  | 2      | 裝備 | 
+----+--------+------+ 

例6: (明確指定索引,並且有此資料,row lock)

console1:

mysql> select * from goods where status=1 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 1  | 1      | 道具 | 
+----+--------+------+ 

console2:查詢status=1的資料時阻塞,超時後返回為空,說明資料被console1鎖定了

mysql> select * from goods where status=1 for update; 
Query OK, -1 rows affected 

console2:查詢status=2的資料,能正常查詢,說明console1只鎖住了行,未鎖表

mysql> select * from goods where status=2 for update; 
+----+--------+------+ 
| id | status | name | 
+----+--------+------+ 
| 2  | 2      | 裝備 | 
+----+--------+------+ 

例7: (明確指定索引,若查無此資料,無lock)
console1:查詢status=3的資料,返回空資料

mysql> select * from t_goods where status=3 for update; 
Empty set 

console2:查詢status=3的資料,返回空資料
Sql程式碼 收藏程式碼

mysql> select * from t_goods where status=3 for update; 
Empty set

 完畢。。。

相關文章