天天看點

MySQL之悲觀鎖場景舉例

轉載于: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為主鍵,資料庫中記錄如下;
           
MySQL之悲觀鎖場景舉例

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

例1: (明确指定主鍵,并且有此資料,row lock)

       console1:查詢出結果,但是把該條資料鎖定了

MySQL之悲觀鎖場景舉例

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

MySQL之悲觀鎖場景舉例

例4: (主鍵不明确,table lock)

MySQL之悲觀鎖場景舉例

以上就是關于資料庫主鍵對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)

MySQL之悲觀鎖場景舉例