【mysql】- 鎖篇


回顧

問題

  • 事務併發執行時可能帶來各類問題,併發事務訪問相同記錄的狀況大體能夠劃分爲3種
    • 讀-讀狀況:即併發事務相繼讀取相同的記錄
      • 讀取操做自己不會對記錄有什麼影響,並不會引發什麼問題,因此容許這種狀況的發
    • 寫-寫狀況:即併發事務相繼對相同的記錄作出改動
      • 任何一種隔離級別都不容許這種問題的發生。因此在多個未提交事務相繼對一條記錄作改動時,須要讓它們排隊執行,這個排隊的過程實際上是經過來實現的。這個所謂的實際上是一個內存中的結構,在事務執行前原本是沒有鎖的,也就是說一開始是沒有鎖結構和記錄進行關聯的
    • 讀-寫寫-讀狀況:也就是一個事務進行讀取操做,另外一個進行改動操做
      • 這種狀況下可能發生髒讀不可重複讀幻讀的問題

解決上述問題方案

  • 讀操做利多版本併發控制( MVCC ),寫操做進行加鎖
  • 讀、寫操做都採用加鎖的方案

一致性讀(Consistent Reads)

  • 事務利用MVCC 進行的讀取操做稱之爲一致性讀,或者一致性性鎖讀,有的地也稱之爲快照讀。全部普通的SELECT語句( plain SELECT )在READ COMMITTEDREPEATABLE READ隔離級別下都算是一致性讀一致性讀並不會對錶中的任何記錄作加鎖操做,其餘事務能夠自由的對錶中的記錄作改動

鎖定讀(Locking Reads)

併發事務的讀-讀狀況並不會引發什麼問題,不過對於寫-寫讀-寫寫-讀這些狀況可能會引發一些問題,須要使用MVCC或者加鎖的方式來解決它們。在使用加鎖的方式解決問題時,因爲既要容許讀-讀狀況不受影響,又要使寫-寫讀-寫寫-讀狀況中的操做相互阻塞併發

  • 共享鎖和獨佔鎖指針

    • 共享鎖 ,英文名:Shared Locks,簡稱S鎖。在事務要讀取一條記錄時,須要先獲取該記錄的S鎖
    • 獨佔鎖 ,也常稱排他鎖,英文名:Exclusive Locks,簡稱X鎖。在事務要改動一條記錄時,須要先獲取該記錄的X鎖
    • 上述兩種鎖的場景
      • 假如事務T1首先獲取了一條記錄的S鎖以後,事務T2接着也要訪問這條記錄:
        • 若是事務T2想要再獲取一個記錄的S鎖,那麼事務T2也會得到該鎖,也就意味着事務T1T2在該記錄上同時持有S鎖
        • 若是事務T2想要再獲取一個記錄的X鎖,那麼此操做會被阻塞,直到事務T1提交以後將S鎖釋放掉
      • 若是事務T1首先獲取了一條記錄的X鎖以後,那麼無論事務T2接着想獲取該記錄的S鎖仍是X鎖都會被阻塞,直到事務T1提交
      • 故咱們說S鎖S鎖是兼容的,S鎖X鎖是不兼容的,X鎖X鎖也是不兼容的
  • 鎖定讀的語句code

    • 在採用加鎖方式解決髒讀不可重複讀幻讀這些問題時,讀取一條記錄時須要獲取該下該記錄的S鎖,其實這是不嚴謹的,有時候想在讀取記錄時就獲取記錄的X鎖,來禁用別的事務讀寫該記錄
      • 對讀取的記錄加S鎖
        • 也就是在普通的SELECT語句後邊加,若是當前事務執行了該語句,那麼它會爲讀取到的記錄加S鎖,這樣容許別的事務繼續獲取這些記錄的S鎖(比方說別的事務也使用語句來讀取這些記錄),可是不能獲取這些記錄的X鎖(比方說使SELECT ... FOR UPDATE語句來讀取這些記錄,或者直接修改這些記錄)。若是別的事務想要獲取這些記錄的X鎖,那麼它們會阻塞,直到當前事務提交以後將這些記錄上的S鎖釋放掉
      • 對讀取的記錄加X鎖
        • SELECT ... FOR UPDATE; 也就是在普通的SELECT語句後邊加FOR UPDATE,若是當前事務執行了該語句,那麼它會爲讀取到的記錄加X鎖,這樣既不容許別的事務獲取這些記錄的S鎖(比方說別的事務使用語句來讀取這些記錄),也不容許獲取這些記錄的X鎖(比方說使用SELECT ... FOR UPDATE語句來讀取這些記錄,或者直接修改這些記錄)。若是別的事務想要獲取這些記錄的S鎖或者X鎖,那麼它們會阻塞,直到當前事務提交以後將這些記錄上的X鎖釋放掉
  • 寫操做索引

    • 日常所用到的寫操做DELETEUPDATEINSERT這三種:
      • DELETE:對一條記錄作DELETE操做的過程實際上是先在B+樹中定位到這條記錄的位置,而後獲取一下這條記錄的X鎖,而後再執行delete mark操做。咱們也能夠把這個定位待刪除記錄在B+樹中位置的過程當作是一個獲取X鎖的鎖定讀
      • UPDATE:
        • 在對一條記錄作UPDATE操做時分爲三種狀況:
          • 若是未修改該記錄的鍵值而且被更新的列佔用的存儲空間在修改先後未發生變化,則先在B+樹中定位到這條記錄的位置,而後再獲取一下記錄的X鎖,最後在原記錄的位置進行修改操做。其實咱們也能夠把這個定位待修改記錄在B+樹中位置的過程當作是一個獲取X鎖鎖定讀
          • 若是未修改該記錄的鍵值而且至少有一個被更新的列佔用的存儲空間在修改先後發生變化,則先在B+樹中定位到這條記錄的位置,而後獲取一下記錄的X鎖,將該記錄完全刪除掉(就是把記錄完全移入垃圾鏈表),最後再插入一條新記錄。這個定位待修改記錄在B+樹中位置的過程當作是一個獲取X鎖鎖定讀,新插入的記錄由 INSERT操做提供的隱式鎖進行保護
          • 若是修改了該記錄的鍵值,則至關於在原記錄上作DELETE操做以後再來一次INSERT操做,加鎖操做就須要按照DELETEINSERT的規則進行了
      • NSERT:通常狀況下,新插入一條記錄的操做並不加鎖,經過一種稱之爲隱式鎖來保護這條新插入的記錄在本事務提交前不被別的事務訪問

前邊提到的都是針對記錄的,也能夠被稱之爲行級鎖或者行鎖,對一條記錄加鎖影響的也只是這條記錄而已,咱們就說這個鎖的粒度比較細;其實一個事務也能夠在表級別進行加鎖,天然就被稱之爲表級鎖或者表鎖,對一個表加鎖影響整個表中的記錄,咱們就說這個鎖的粒度比較粗。給表加的鎖也能夠分爲共享鎖S鎖)和獨佔鎖X鎖事務

  • 多粒度鎖
    • 給表加S鎖
      • 若是一個事務給表加了S鎖,那麼:
        • 別的事務能夠繼續得到該表的S鎖
        • 別的事務能夠繼續得到該表中的某些記錄的S鎖
        • 別的事務不能夠繼續得到該表的X鎖
        • 別的事務不能夠繼續得到該表中的某些記錄的X鎖
    • 給表加X鎖
      • 若是一個事務給表加了X鎖(意味着該事務要獨佔這個表),那麼:
        • 別的事務不能夠繼續得到該表的S鎖
        • 別的事務不能夠繼續得到該表中的某些記錄的S鎖
        • 別的事務不能夠繼續得到該表的X鎖
        • 別的事務不能夠繼續得到該表中的某些記錄的X鎖

InnoDB存儲引擎中的鎖

  • 表級鎖
    • 表級別的S鎖X鎖
      • 在對某個表執行SELECTINSERTDELETEUPDATE語句時,InnoDB存儲引擎是不會爲這個表添加表級別的S鎖或者X鎖
    • 表級別的IS鎖IX鎖
      • 當咱們在對使用InnoDB存儲引擎的表的某些記錄加S鎖以前,那就須要先在表級別加一個IS鎖,當咱們在對使用InnoDB 存儲引擎的表的某些記錄加X鎖以前,那就須要先在表級別加一個IX鎖IS鎖IX鎖的使命只是爲了後續在加表級別的S鎖X鎖時判斷表中是否有已經被加鎖的記錄,以免用遍歷的方式來查看錶中有沒有上鎖的記錄
    • 表級別的AUTO-INC鎖
      • 在使用MySQL過程當中,咱們能夠爲表的某個列添加AUTO_INCREMENT屬性,以後在插入記錄時,能夠不指定該列的值,系統會自動爲它賦上遞增的值
  • 行級鎖:
    • 行鎖,也稱爲記錄鎖 ,顧名思義就是在記錄上加的鎖
    • 行鎖類型
      • Record Locks:有S鎖X鎖之分,當一個事務獲取了一條記錄的S型記錄鎖後,其餘事務也能夠繼續獲取該記錄的S型記錄鎖,但不能夠繼續獲取X型記錄鎖;當一個事務獲取了一條記錄的X型記錄鎖後,其餘事務既不能夠繼續獲取該記錄的S型記錄鎖,也不能夠繼續獲取X型記錄鎖
      • Gap Locks:能夠用於解決可重複讀級別下的幻讀問題,防止插入幻影記錄
      • Next-Key Locks: 鎖住某條記錄同時又能夠阻止其餘事務在該條記錄前邊的間隙插入新紀錄,其是Record Locksgap Locks的合體它既能保護該條記錄,又能阻止別的事務將新記錄插入被保護記錄前邊的間隙
      • Insert Intention Locks:一個事務在插入一條記錄時須要判斷一下插入位置是否是被別的事務加了所謂的gap鎖,若存在,插入操做須要等待,直到擁有的gap鎖對應的事務提交,事務在等待的時候也須要在內存中生成一個鎖結構,代表有事務想在某個間隙中插入新紀錄,可是如今在等待。這種類型的鎖命名爲Insert Intention Locks,也成爲插入意向鎖
      • 隱式鎖:一個事務在執行INSERT操做時,若是即將插入的間隙已經被其餘事務加了gap鎖,那麼本次INSERT操做會阻塞,而且當前事務會在該間隙上加一個插入意向鎖,不然通常狀況下INSERT操做是不加鎖的

內存結構

  • 加鎖的本質就是在內存中建立一個鎖結構與之關聯
    • 對於須要放在同一個鎖結構中的狀況
      • 在同一個事務中進行加鎖操做
      • 被加鎖的記錄在同一個頁面中
      • 加鎖的類型是同樣的
      • 等待狀態是同樣的
  • 鎖結構
    • 鎖所在的事務信息: 不管是表鎖仍是行鎖,都是在事務執行過程當中生成的,哪一個事務生成了這個鎖結構,這裏就記載着這個事務的信息。其本質是一個指針,經過該指針能夠找到內存中關於該事務的更多信息
    • 索引信息:對於行鎖來講,須要記錄一下加鎖的記錄是屬於哪一個索引的
    • 表鎖/行級信息
      • 表鎖:記載着這是對哪一個表加的鎖,還有其餘的一些信息
      • 行鎖:記載了三個重要的信息
        • Space ID :記錄所在表空間。
        • Page Number :記錄所在頁號。
        • n_bits :對於行鎖來講,一條記錄就對應着一個比特位,一個頁面中包含不少記錄,用不一樣的比特位來區分究竟是哪一條記錄加了鎖。爲此在行鎖結構的末尾放置了一堆比特位,這個n_bits屬性表明使用了多少比特位
    • type_mode:這是一個32位的數,被分紅了lock_modelock_typerec_lock_type三個部分
      • 鎖的模式( lock_mode ),佔用低4位
        • LOCK_IS(十進制的0):表示共享意向鎖,也就是IS鎖
        • LOCK_IX(十進制的1):表示獨佔意向鎖,也就是IX鎖
        • LOCK_S(十進制的2):表示共享鎖,也就是S鎖
        • LOCK_X(十進制的3):表示獨佔鎖,也就是X鎖
        • LOCK_AUTO_INC(十進制的4):表示AUTO-INC鎖
      • 鎖的類型( lock_type ),佔用第5〜8位,不過現階段只有第5位和第6位被使用:
        • LOCK_TABLE(十進制的16),也就是當第5個比特位置爲1時,表示表級鎖。
        • LOCK_REC(十進制的32),也就是當第6個比特位置爲1時,表示行級鎖
      • 行鎖的具體類型(rec_lock_type),使用其他的位來表示。只有在lock_type的值爲LOCK_REC時,也就是隻有在該鎖爲行級鎖時,纔會被細分爲更多的類型:
        • LOCK_ORDINARY(十進制的0):表示next-key鎖 。
        • LOCK_GAP(十進制的512):也就是當第10個比特位置爲1時,表示gap鎖
        • LOCK_REC_NOT_GAP(十進制的1024):也就是當第11個比特位置爲1時,表示記錄鎖
        • LOCK_INSERT_INTENTION(十進制的2048):也就是當第12個比特位置爲1時,表示插入意向鎖
相關文章
相關標籤/搜索