1. 程式人生 > >mysql的兩階段協議(封鎖定理,蟲洞事務)

mysql的兩階段協議(封鎖定理,蟲洞事務)

  我們都知道資料庫的事務具有ACID的四個屬性:原子性,一致性,隔離性和永續性。然後在多執行緒操作的情況下,如果不能保證事務的隔離性,就會造成資料的修改丟失(事務2覆蓋了事務1的修改結果)、讀到髒資料(事務2讀到了事務1未回滾的資料)、不可重讀(事務2讀到了事務1未提交的修改)和幻讀(事務2讀到了事務1未提交的增刪)等。保證事務隔離性可以防止事務出現以上問題,那麼資料庫又是怎麼來保證事務的隔離性的呢?

  mysql使用兩階段協議來保證事務執行的序列化從而保證事務的隔離性的。
  首先,為了保證資料訪問的序列,每個資料資源都有對應的鎖(mysql的鎖只細化到行級),那麼只要保證每個資料資源的訪問都是序列化的,然後整個資料庫不就可以保證每個資料操作之間的隔離性了麼,就是說:

  只要每個資料操作都有對應的鎖覆蓋,然後把每個操作都加上對應的鎖,這樣可以保證事務執行的序列化麼?

  答案是:可以保證,但是有個前提是每個事務只包含對一個數據的操作。事實上mysql也預設是這樣做的:如果使用者沒有手動開啟事務,那麼根據AUTOCOMMIT機制,mysql預設將每一個數據庫操作封裝成一個事務來處理。

  然而實際情況中,很多時候一個事務不只包含一個數據庫操作,比如電商平臺在生成訂單時,一個事務裡包含了對訂單表、使用者表、商品表等的操作。在多執行緒操作的情況下,如果只用資料庫的鎖來保證的話,就會出現上面所提到的一些資料異常情況。這時候就需要兩階段協議來保證事務的隔離性了。那麼進入今天的主題:

  兩階段協議如何保證多事務操作之間的隔離性?

  上面說過,只要事務執行是序列的,事務之間就是隔離的,在網上搜索兩階段協議為何能保證序列化,只搜到一個封鎖定理:如果事務是良構的且是兩階段的,那麼任何一個合法的排程都是可序列化的。 既然是定理,那一定是嚴格成立且可證明的,於是順藤摸瓜,找到了封鎖定理的證明(<事務處理:概念與技術>>這本書的7.5.8.2節)。

  首先我們解析命題:

  條件:良構的事務、兩階段、合法的排程

  結論:是可序列化的。

  在書上找到條件的描述:

  如果事務的每個READ、WRITE、 UNLOCK都被響應的鎖覆蓋,且所有的鎖都是在事務結束時釋放,那麼稱這樣的事物是良構的。(mysql的表和行資料在被操作時,都有對應的鎖覆蓋)

 

  如果一個事務可以分成兩個階段,只請求封鎖的擴充套件階段和只釋放鎖的收縮階段,那麼稱之為兩階段的事務。(mysql採用兩階段封鎖協議)

  排程是一組事務的操作的某種合併結果。(mysql支援多事務並行執行)

  一個排程是合法的,如果同一時間沒發生兩個不同事務的鎖衝突。(mysql需要保證並行事務不衝突)

  因為每個事務T都是良構的,所以必定對應著一個解鎖操作Unlock,事務T的解鎖操作步驟在排程中的索引為St。

  假設在排程中事務T1在事務T2之前執行,T1<<<T2,則事務T2依賴事務T1,使得事務T1對物件O的操作步驟a1在事務T2對物件O的操作步驟a2之前,那麼a1和a2必定有一個步驟物件O正覆蓋著排他鎖(WRITE)(因為如果是兩個挨著的讀事務(READ)不存在依賴關係,所以他們的執行順序沒有明確的等價執行序列),如果是a1時物件O覆蓋著一個XLOCK(排他鎖),a2時物件O應該覆蓋著一個SLOCK(共享鎖)或XLOCK(排他鎖),與前面的XLOCK互斥,又由於是兩階段的,所以在a1和a2之間,會存在一個事務T1給物件O解鎖的Unlock操作步驟St1,事務T2的解鎖操作步驟St2在a2之後,所以有:St1<<<St2,即事務T1的解鎖在事務T2的解鎖之前。同理在a2是WRITE操作的時候也成立。如果排程是不可序列化的,那麼必然存在T1<<<T2時,St2<<<St1,與上述結論矛盾,所以:

  如果一個排程包含的事物都是良構的且為兩階段的,那麼該排程是可序列化的。

  可能有人會問,為什麼不給每個事務加排他鎖來保證多事務執行的序列化呢?

  是因為如果把讀鎖也定義為排他鎖的話,會大大的降低併發讀的效率,而查詢操作在資料庫的操作中佔了很大的比例,所以mysql預設採用的可重複讀的隔離級別,用兩階段協議和全場景覆蓋的鎖來保證執行結果的序列化,並沒有採用全用排他鎖來保證序列化。