悲觀鎖/樂觀鎖

樂觀鎖和悲觀鎖是并發控制主要采用的技術手段。為什么有了事務這東西,還需要樂觀鎖悲觀鎖?

比如搶票,假設余票只有1張;隔離級別可以保證事務A和事務B不能讀到對方的數據,也不能更新對方正在更新的數據,但是事務A和事務B都認為還有1張余票,于是出票,并更新為0。但是事務B讀取的是過時數據,依據過時數據做了業務處理。所以需要樂觀鎖或者悲觀鎖,來記錄一個信息:當前已經讀取的數據,是不是已經過時了。事務可以保證一組操作的原子性和隔離級別,悲觀鎖/樂觀鎖用來保證并發性。
將事務隔離級別設置為串形化也可以保證數據在多事務并發處理下不存在數據不一致的問題,但串行執行使得數據庫的處理性能大幅度地下降。一般來說,數據庫的隔離級別都會設置為read committed(MySQL為RR),然后由應用程序使用樂觀鎖/悲觀鎖來彌補數據不一致的問題。

悲觀鎖(Pessimistic Lock),每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。悲觀鎖往往依靠數據庫提供的鎖機制。

樂觀鎖(Optimistic Lock),每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號,最后更新時間等機制。如果其他事務有更新的話,正在提交的事務會進行回滾。一般通過程序實現。

悲觀鎖:
要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性,因為MySQL默認使用autocommit模式,也就是說,當你執行一個更新操作后,MySQL會立刻將結果進行提交。

    set autocommit=0;

使用select…for update的方式,就通過數據庫實現了悲觀鎖(MySQL MVCC的當前讀,加了排它鎖)。此時在t_goods表中,id為1的 那條數據就被我們鎖定了,其它的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。在事務中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一筆數據時會等待其它事務結束后才執行,一般的SELECT ... 則不受此影響。

    //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;

MySQL InnoDB默認Row-Level Lock,只有「明確」地指定主鍵或索引,MySQL 才會執行Row lock (只鎖住被選取的數據) ,否則MySQL 將會執行Table Lock (將整個數據表單給鎖住)。

樂觀鎖:
使用版本號實現樂觀鎖:
使用版本號時,可以在數據初始化時指定一個版本號,每次對數據的更新操作都對版本號執行+1操作。并判斷當前版本號是不是該數據的最新的版本號。

    1.查詢出商品信息
    select (status,status,version) from t_goods where id=#{id}
    2.根據商品信息生成訂單
    3.修改商品status為2
    update t_goods 
    set status=2,version=version+1
    where id=#{id} and version=#{version};

樂觀并發控制相信事務之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候才去驗證,不會產生任何鎖和死鎖。如果大量回滾也會影響效率。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容