轉載于:mysql的select … for update
關于mysql的間隙鎖和臨建鎖案例 間隙鎖與臨建鎖案例
最近的項目中,因為涉及到Mysql資料中樂觀鎖和悲觀鎖的使用,是以結合項目和網上的知識點對樂觀鎖和悲觀鎖的知識進行總結。
悲觀鎖介紹
悲觀鎖是對資料被的修改持悲觀态度(認為資料在被修改的時候一定會存在并發問題),是以在整個資料處理過程中将資料鎖定。悲觀鎖的實作,往往依靠資料庫提供的鎖機制(也隻有資料庫層提供的鎖機制才能真正保證資料通路的排他性,否則,即使在應用層中實作了加鎖機制,也無法保證外部系統不會修改資料)。
使用場景舉例:
商品goods表中有一個字段status,status為1代表商品未被下單,status為2代表商品已經被下單,那麼我們對某個商品下單時必須確定該商品status為1。假設商品的id為1。如果不采用鎖,那麼操作方法如下:
//1.查詢出商品資訊
select status from t_goods where id=1;
//2.根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
上面這種場景在高并發通路的情況下很可能會出現問題。前面已經提到,隻有當goods status為1時才能對該商品下單,上面第一步操作中,查詢出來的商品status為1。但是當我們執行第三步Update操作的時候,有可能出現其他人先一步對商品下單把goods status修改為2了,但是我們并不知道資料已經被修改了,這樣就可能造成同一個商品被下單2次,使得資料不一緻。是以說這種方式是不安全的。
使用悲觀鎖來實作
在上面的場景中,商品資訊從查詢出來到修改,中間有一個處理訂單的過程,使用悲觀鎖的原理就是,當我們在查詢出goods資訊後就把目前的資料鎖定,直到我們修改完畢後再解鎖。那麼在這個過程中,因為goods被鎖定了,就不會出現有第三者來對其進行修改了。要使用悲觀鎖,我們必須關閉mysql資料庫的自動送出屬性。
set autocommit=0;
//設定完autocommit後,我們就可以執行我們的正常業務了。具體如下:
//0.開始事務
begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品資訊
select status from t_goods where id=1 for update;
//2.根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.送出事務
commit;/commit work;
注:上面的
begin/commit
為事務的開始和結束,因為在前一步我們關閉了
mysql的autocommit
,是以需要手動控制事務的送出,在這裡就不細表了。
上面的第一步我們執行了一次查詢操作:
select status from t_goods where id=1 for update;
與普通查詢不一樣的是,我們使用了
select…for update
的方式,這樣就通過資料庫實作了悲觀鎖。此時在t_goods表中,id為1的那條資料就被我們鎖定了,其它的事務必須等本次事務送出之後才能執行。這樣我們可以保證目前的資料不會被其它事務修改。
注:需要注意的是,在事務中,隻有
SELECT ... FOR UPDATE 或LOCK IN SHARE MODE
相同資料時會等待其它事務結束後才執行,一般SELECT … 則不受此影響。拿上面的執行個體來說,當我執行
select status from t_goods where id=1 for update;
後。我在另外的事務中如果再次執行
select status from t_goods where id=1 for update;
則第二個事務會一直等待第一個事務的送出,此時第二個查詢處于阻塞的狀态,但是如果我是在第二個事務中執行
select status from t_goods where id=1;
則能正常查詢出資料,不會受第一個事務的影響。
補充:MySQL select…for update的Row Lock與Table Lock
上面我們提到,使用
select…for update
會把資料給鎖住,不過我們需要注意一些鎖的級别,MySQL InnoDB預設
Row-Level Lock
,是以隻有「明确」地指定主鍵,MySQL 才會執行Row lock (隻鎖住被選取的資料) ,否則MySQL 将會執行Table Lock (将整個資料表單給鎖住)。衆多資料中都說innodb使用的是行級鎖,但實際上是有限制的。隻有在你增删改查時比對的條件字段帶有索引時,innodb才會使用行級鎖,在你增删改查時比對的條件字段不帶有索引時,innodb使用的将是表級鎖。因為當你比對條件字段不帶有所引時,資料庫會全表查詢,是以這需要将整張表加鎖,才能保證查詢比對的正确性。在生産環境中我們往往需要滿足多人同時對一張表進行增删改查,是以就需要使用行級鎖,是以這個時候一定要記住為比對條件字段加索引。
舉例子說明:
資料庫表t_goods,包括id,status,name三個字段,id為主鍵,資料庫中記錄如下;
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnLlJDOwYDZ0UzYhdTZzEGO5YTZ1QTO3M2YlRmZjZmN3E2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
注:為了測試資料庫鎖,我使用兩個console來模拟不同的事務操作,分别用console1、console2來表示。
例1: (明确指定主鍵,并且有此資料,row lock)
console1:查詢出結果,但是把該條資料鎖定了
例2: (明确指定主鍵,若查無此資料,無lock)
console1
:查詢結果為空
1. set autocommit=0;
2. SELECT * from t_goods where id=4 for update;
console2:
查詢結果為空,查詢無阻塞,說明console1沒有對資料執行鎖定
set autocommit=0;
SELECT * from t_goods where id=4 for update;
例3: (無主鍵,table lock)
例4: (主鍵不明确,table lock)
以上就是關于資料庫主鍵對MySQL鎖級别的影響執行個體,需要注意的是,除了主鍵外,使用索引也會影響資料庫的鎖定級别。
舉例:我們修改t_goods表,給status字段建立一個索引。
**例5: **(明确指定索引,并且有此資料,row lock)
console1:
1. set autocommit=0;
2. SELECT * from t_goods where status=1 for update;
console2:查詢status=1的資料時阻塞,逾時後傳回為空,說明資料被console1鎖定了
例7: (明确指定索引,若查無此資料,無lock)