1. 程式人生 > >Mysql的排他鎖和共享鎖

Mysql的排他鎖和共享鎖

狀態 無法 基本概念 parent 數據庫數據 不能 完成 使用場景 增加

  今天看代碼看到有select name from user where id = 1 for update,有點懵逼,完全沒有見過,只能說自己見識少了,那就只能學習一下。先做一下基本知識了解(大部分都是整理了別人的文檔,如有侵權還請告知):

鎖的基本概念
  當多事務爭取一個資源時,有可能導致數據不一致,這個時候需要一種機制限制,並且將數據訪問順序化,用來保證數據庫數據的一致性,鎖就是其中的一種機制。我們可以用商場的試衣間來做個比喻,商場裏得每個試衣間都可供多個消費者使用,因此可能出現多個消費者同時試衣服需要使用試衣間,這時候就產生沖突了,為了避免沖突,試衣間裝了鎖(其實就是進去之後把門拴住),某一個試衣服的人在試衣間裏把鎖鎖住了,其他顧客就不能再從外面打開了,只能等待裏面的顧客,試完衣服,從裏面把鎖打開,外面的人才能進去(網上找到的比喻,非常形象)。不過我想要是並發了就尷尬了,哈哈。

鎖的基本類型
  數據庫上的操作可以歸納為兩種:讀和寫。
  多個事務同時讀取一個對象的時候,是不會有沖突的。同時讀和寫,或者同時寫才會產生沖突。因此為了提高數據庫的並發性能,通常會定義兩種鎖:共享鎖和排它鎖。

共享鎖(Shared Lock,也叫S鎖)
  共享鎖(S)表示對數據進行讀操作。因此多個事務可以同時為一個對象加共享鎖。(如果試衣間的門還沒被鎖上,顧客都能夠同時進去參觀)
  產生共享鎖的sql:select * from ad_plan lock in share mode;

共享鎖的使用場景
  SELECT ... LOCK IN SHARE MODE走的是IS鎖(意向共享鎖),即在符合條件的rows上都加了共享鎖,這樣的話,其他人可以讀取這些記錄,也可以繼續添加IS鎖,但是無法修改這些記錄直到你這個加鎖的過程執行完成(完成的情況有:事務的提交,事務的回滾,否則直接鎖等待超時)。

  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)就不會存在這種問題了。

排他鎖(Exclusive Lock,也叫X鎖)

  排他鎖也叫寫鎖(X)。

  排他鎖表示對數據進行寫操作。如果一個事務對對象加了排他鎖,其他事務就不能再給它加任何鎖了。(某個顧客把試衣間從裏面反鎖了,其他顧客想要使用這個試衣間,就只有等待鎖從裏面給打開了)
產生排他鎖的sql: select * from ad_plan for update;看到了吧,for update出現了,所以for update 是排他鎖,漲知識了。
排他鎖的使用場景:

  使用場景一:訂單的商品數量
 
   但是如果是同一張表的應用場景,舉個例子,電商系統中計算一種商品的剩余數量,在產生訂單之前需要確認商品數量>=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時必然會產生死鎖導致事務回滾。以下是操作範例(按時間順序)

  使用場景一:數據表的狀態

    如果存在一張表記錄一個商品的狀態,在訂單的變化過程中,訂單的狀態是不斷變化的,而且變化的過程中肯定也會有並發的問題,而且很多時候與其他系統有交互,會存在補償的情況,所以並發的可能性很大,補償或者為了增加狀態修改的成功可能性,2次改變狀態的情況也有,樓主就遇到了這種情況,真操蛋。於是看到有這樣的for update寫法。

    1 update order set status = 1 where product_id = ‘1‘;   

    2 insert order_flow (..............) value (.........)

    這樣的情況下就有可能訂單的狀態已經更新完成了,但是補償這些額外的消息把狀態又更新為待處理或者插入了多條流水的情況(多條流水的可能性大,狀態的那種可能補償滯後)。這個時候就可以使用select .... from order where order_id = ‘1‘ for update,先鎖住要修改狀態的表,這樣就不會別人操作了,自己先後面把流水插入,然後更新狀態,完美。但是加了鎖之後性能就很慢了,擔心性能影響,而且有可能存在死鎖的情況,後面我就修改為流水表中增加一個唯一索引,這樣插入流水報錯就是已經處理過的記錄了。這樣就不會存在性能問題。

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

Mysql的排他鎖和共享鎖