MySQL系列(五) 鎖

  • 13 鎖

    鎖機制用於管理對共享資源的併發訪問。算法

    InnoDB 鎖的實現與 Oracle 數據庫很是相似。提供一致性的非鎖定讀、行級鎖支持。行級鎖沒有額外的開銷,並能夠同時獲得併發行和一致性。數據庫

    • lock 和 latch併發

      lock 和 latch 均可以成爲鎖,可是二者有大相徑庭的意義。性能

      latch 通常稱爲閂鎖,輕量級的鎖,要求鎖定的時間必須很是短,其目的是用來保證併發線程操做臨界資源的正確性,沒有死鎖的檢測。線程

      lock 的對象是事務鎖,用來鎖定的是數據庫的如表、頁、行等。而且通常lock 的對象僅在事務提交或回滾以後被釋放。lock 有死鎖機制。設計

    • 鎖的 類型版本控制

      • 共享鎖: 容許事務讀一行數據 S鎖
      • 排他鎖:容許事務刪除或更新一行數據 X鎖
        • 將事務串行化,必須等待鎖資源的釋放。
        • 數據庫會對DML操做自動加 X鎖。

      共享鎖和共享鎖兼容,其餘任何組合都不兼容日誌

      • 意向鎖: 將鎖定的對象分爲多個層次,意味着事務但願在更細的粒度上加鎖。
    • 一致性非鎖定讀code

      一致性的非鎖定讀是指 InnoDB 存儲引擎經過行多版本控制的方式來讀取當前執行時間數據庫中行的數據。若是讀取的行正在執行 DELETE 或 UPDATE 操做,這時讀取操做不會所以去等待行上鎖的釋放。相反地,InnoDB存儲引擎會去讀取行的一個快照數據。對象

      快照數據是指該行的以前版本的數據,該實現是經過 undo(重作日誌) 段來完成。而undo 用來在事務中回滾數據,所以快照數據自己是沒有額外的開銷。此外,讀取快照數據是不須要上鎖的,由於沒有事務須要對歷史的數據進行修改操做。

      非鎖定讀機制極大地提升了數據庫的併發性。在InnoDB存儲引擎的默認設置下,這是默認的讀取方式,即讀取不會佔用和等待表上的鎖。

      可是在不一樣事務隔離級別下,讀取的方式不一樣,並非在每一個事務隔離級別下都是採用一致性非鎖定讀。此外,即便都是使用非鎖定的一致性讀,可是對於快照數據的定義也各不相同。

      REPEATABLE READ InnoDB存儲引擎的默認事務隔離級

    • 一致性鎖定讀

      在默認配置下,InnoDB存儲引擎的SELECT操做使用一致性非鎖定讀。可是在某些狀況下,用戶須要顯式地對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。而這要求數據庫支持加鎖語句,即便是對於SELECT的只讀操做。

      支持的鎖定讀操做:

      • SELECT…FOR UPDATE 加X鎖
      • SELECT…LOCK IN SHARE MODE 加S鎖
    • 行鎖的三種算法

      • 單行鎖,做用域單行記錄的鎖
      • 間隙鎖,鎖定一個範圍,但不包括自己
        • 爲了阻止多個事務將記錄插入到同一範圍中,爲了防止幻讀
      • 間隙鎖+單行鎖,Next-Key lock
        • 設計的目的主要是爲了解決幻讀問題
        • 鎖定的不是一個值,而是一個範圍
        • 當查詢的索引含有惟一屬性時,會降級爲單行鎖
    • 幻讀問題

      幻讀是指在同一事物下,連續執行兩次一樣的SQL語句,會產生不一樣的結果。

      InnoDB 採用的是行鎖的第三種算法: 間隙鎖+單行鎖 來解決幻讀問題。

      假設表中有(1,2,5)三個id,有兩個事務:

      事務1: ... where id>2;

      事務2: ... insert id=4;

      若是事務1先於事務2,但沒有提交;此時執行事務2,而且數據庫容許,那麼事務1提交時的結果就被改變了。即當前事務能夠看到其餘事務的結果,違反了事務的隔離性

      事務默認的隔離級別爲 REPEATABLE READ 不可重讀,其採用 Next-Key lock 的方式來加鎖,即對大於2的範圍所有加鎖,所以事務2的插入即是不被容許的,只能處於阻塞狀態,等待事務1鎖的釋放。

      而在隔離級別爲 READ COMMITTED 不可重複讀下,其僅採用單行鎖的方式。

      另外,Next-Key Locking機制在應用層面實現惟一性的檢查。假設用戶查詢一個索引,若是值不存在則插入一行數據。這不會存在任何問題。

      可是若是併發的執行,就會致使死鎖,只有一個事務的插入操做會成功,其他的都會拋出死鎖的錯誤。

    • 併發時鎖帶來的三個問題

      經過鎖機制能夠實現事務的隔離性要求,使得事務能夠併發地工做。鎖提升了併發性,可是卻會帶來潛在的問題。不過也正是由於事務隔離性的要求,鎖通常只會帶來三種問題。

      • 1 髒讀

        髒數據是事務對緩衝池中行記錄的修改,而且尚未被提交的數據。

        髒讀指不一樣的事務間,當前事務能夠讀到另外事務的髒數據。

        髒讀發生的條件須要事務的隔離級別爲 READ UNCOUNCOMITTED 讀未提交。等級最低的隔離級別。

      • 2 不可重複讀

        不可重複讀是指在一個事務內屢次讀取同一數據集合。在這個事務尚未結束時,另一個事務也訪問該同一數據集合,並作了一些DML操做。所以,在第一個事務中的兩次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的狀況,這種狀況稱爲不可重複讀,也稱之爲幻讀。

        不可重複讀和髒讀的區別是:髒讀是讀到未提交的數據,而不可重複讀讀到的倒是已經提交的數據,其違反了數據庫事務一致性的要求。

      • 3 丟失更新

        丟失更新是另外一個鎖致使的問題,簡單來講其就是一個事務的更新操做會被另外一個事務的更新操做所覆蓋,從而致使數據的不一致。

        可是,在當前數據庫隔離級別下,對於行的DML(數據操做語言),須要對行或更粗粒度對象的加鎖,所以第二個事務不能對同一個記錄進行DML操做,其會被阻塞,直到一個事務提交。

        另外一個典型的問題,在多用戶計算機系統下:

        1)事務T1查詢一行數據,放入本地內存,並顯示給一個終端用戶User1。

        2)事務T2也查詢該行數據,並將取得的數據顯示給終端用戶User2。

        3)User1修改這行記錄,更新數據庫並提交。

        4)User2修改這行記錄,更新數據庫並提交。

        User2提交的數據可能會覆蓋User1提交的數據。

        要避免這種問題,就要讓事務這種狀況下的操做變爲可串行化,而不是並行的發生,即加上一個排他鎖。

    • 悲觀鎖和樂觀鎖

      主要是爲了解決丟失更新的問題

      • 悲觀鎖
        • 描述
          • 顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。
          • 傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。
          • 它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。
          • 悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。
        • 定義
          • 屬於 X 鎖,排他鎖
          • 隔離級別屬於可重複讀級別,不會產生幻讀等問題
          • 悲觀鎖通常是用於併發不是很高,而且不容許髒讀等狀況。可是對數據庫資源消耗較大。
        • 使用
          • 關閉MySQL的自動提交
          • 使用一致性鎖定讀的X鎖
      • 樂觀鎖
        • 描述

          • 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖
          • 可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。
          • 樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫若是提供相似於write_condition機制的其實都是提供的樂觀鎖。
        • 定義

          • 不屬於數據庫內部的鎖,人爲定義的鎖
          • 隔離級別對應不可重複讀,不然數據庫對DML操做自動加排他鎖
          • 所以會出現幻讀的狀況,解決的方式是編寫代碼實現的版本控制
          • 性能好,支持的併發也更多
          • 樂觀鎖是首先假設數據衝突不多,只有在數據提交修改的時候才進行校驗,若是衝突了則不會進行更新。
        • 使用

          一般的實現方式增長一個version字段,爲每一條數據加上版本。每次更新的時候version+1,而且更新時候帶上版本號。

          更新完成時,須要從數據庫獲取當前version的值,若是和最開始獲取的值同樣,則表示這段時間沒有執行DML操做,能夠進行當前事務的執行。不然不行。

          數據庫會對DML操做自動加X鎖,即排他鎖。所以這段更新的期間,保證只有當前一個事務在執行。所以執行完成後,其餘一樣version的事務自動變爲死鎖被kill掉。

    • 死鎖

      • 死鎖的描述和解決

        死鎖是指兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象。若無外力做用,事務將沒法推動下去。

        解決死鎖問題最簡單的方式是不要有等待,將任何的等待都轉化爲回滾,而且事務從新開始。可是,這會致使併發性能的大幅降低,並且是沒法察覺的,是比死鎖更加嚴重的問題。

        解決死鎖問題較簡單的一種方法是超時,當兩個事務互相等待時,其中一個等待時間超過設置的某一閾值時,那麼這個事務就進行回滾。所以另外一個等待的事務就能進行下去。在InnoDB存儲引擎中,參數innodb_lock_wait_timeout用來設置超時的時間。

        超時機制雖然簡單,可是其僅經過超時後對事務進行回滾的方式來處理,或者說其是根據FIFO的順序選擇回滾對象。但若超時的事務所佔權重比較大,如事務操做更新了不少行,佔用了較多的重作日誌記錄,這時採用FIFO的方式,就顯得不合適了,由於回滾這個事務的時間相對另外一個事務所佔用的時間可能會不少。

        較之超時機制,當前數據庫廣泛採用 等待圖 的方式來主動檢測死鎖。等待圖要求數據庫保存兩項信息:

        • 鎖的信息鏈表
        • 事務等待鏈表

        經過上述鏈表能夠構造出一張,其採用深度優先的算法。若是圖中存在迴路,就表明死鎖的存在。

        若存在死鎖,InnoDB會當即回滾一個事務,一般選擇回滾undo量最小的事務。

      • 死鎖的示例

        AB-BA 死鎖,二者互相等待資源的釋放

         

        Oracle 常見的死鎖是沒有對外鍵添加索引,而MySQL會對其自動添加。

        此外還存在一種死鎖,當前事務持有了帶插入記錄的下一個記錄的排他鎖,可是在等待隊列還有一個共享鎖的請求,則可能會發生死鎖。

         

        會話A中已經對記錄4持有了X鎖,可是會話A中插入記錄3時會致使死鎖發生。這個問題的產生是因爲會話B中請求記錄4的S鎖而發生等待,但以前請求的鎖對於主鍵值記錄一、2都已經成功,若在事件點5能插入記錄,那麼會話B在得到記錄4持有的S鎖後,還須要向後得到記錄3的記錄,這樣就顯得有點不合理。所以InnoDB存儲引擎在這裏主動選擇了死鎖,而回滾的是undo log記錄大的事務,這與AB-BA死鎖的處理方式又有所不一樣。

    • 鎖升級

      • 概念

        鎖升級是指將當前鎖的粒度下降。舉例來講,數據庫能夠把一個表的1000個行鎖升級爲一個頁鎖,或者將頁鎖升級爲表鎖。若是在數據庫的設計中認爲鎖是一種稀有資源,並且想避免鎖的開銷,那數據庫中會頻繁出現鎖升級現象。

      • SQL Service 對鎖升級的處理

        Microsoft SQL Server數據庫的設計認爲鎖是一種稀有的資源,在適合的時候會自動地將行、鍵或分頁鎖升級爲更粗粒度的表級鎖。這種升級保護了系統資源,防止系統使用太多的內存來維護鎖,在必定程度上提升了效率。

      • InnoDB 對鎖升級的處理

        InnoDB存儲引擎不存在鎖升級的問題。由於其不是根據每一個記錄來產生行鎖的,相反,其根據每一個事務訪問的每一個頁對鎖進行管理的,採用的是位圖的方式。所以無論一個事務鎖住頁中一個記錄仍是多個記錄,其開銷一般都是一致的。

相關文章
相關標籤/搜索