1. 程式人生 > >資料庫 樂觀鎖/悲觀鎖 的介紹及應用

資料庫 樂觀鎖/悲觀鎖 的介紹及應用

在處理併發問題時,我們會經常遇到以下2點問題

  1. 更新丟失. 如:使用者A把值從 ‘一等獎’ 改為 ‘二等獎’,使用者B把值從 ‘一等獎’ 改為 ‘三等獎’,則使用者A的更新被覆蓋.
  2. 髒讀. 如:使用者A,B看到的值都是 ‘一等獎’ ,使用者B把值改為 ‘二等獎’,使用者A讀到的值仍為 ‘一等獎’

併發控制的主要採用的技術手段

樂觀鎖、悲觀鎖。

什麼是鎖

鎖是網路資料庫中的一個非常重要的概念,當多個使用者同時對資料庫併發操作時,會帶來資料不一致的問題,所以,鎖主要用於多使用者環境下保證資料庫完整性和一致性。

鎖分類

  1. 從資料庫系統角度分為三種:排他鎖、共享鎖、更新鎖。
  2. 從程式設計師角度分為兩種:一種是悲觀鎖,一種樂觀鎖。。

悲觀鎖(Pessimistic Lock)

悲觀鎖(Pessimistic Lock):正如其名,具有強烈的獨佔和排他特性。它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。

傳統的關係資料庫裡用到了很多這種鎖機制,比如行鎖、表鎖、讀鎖、寫鎖等,都是在操作之前先上鎖。

悲觀鎖按使用性質劃分

共享鎖(Share Lock)

S鎖,也叫讀鎖,用於所有的只讀資料操作。共享鎖是非獨佔的,允許多個併發事務讀取其鎖定的資源。

性質

  1. 多個事務可封鎖同一個共享頁;
  2. 任何事務都不能修改該頁;
  3. 通常是該頁被讀取完畢,S鎖立即被釋放。

例如

在SQL Server中,預設情況下,資料被讀取後,立即釋放共享鎖。 例如,執行查詢語句“SELECT * FROM my_table”時,首先鎖定第一頁,讀取之後,釋放對第一頁的鎖定,然後鎖定第二頁。這樣,就允許在讀操作過程中,修改未被鎖定的第一頁。 例如,語句“SELECT * FROM my_table HOLDLOCK”就要求在整個查詢過程中,保持對錶的鎖定,直到查詢完成才釋放鎖定。

排他鎖(Exclusive Lock)

X鎖,也叫寫鎖,表示對資料進行寫操作。如果一個事務對物件加了排他鎖,其他事務就不能再給它加任何鎖了。(某個顧客把試衣間從裡面反鎖了,其他顧客想要使用這個試衣間,就只有等待鎖從裡面打開了。)

性質

  1. 僅允許一個事務封鎖此頁;
  2. 其他任何事務必須等到X鎖被釋放才能對該頁進行訪問;
  3. X鎖一直到事務結束才能被釋放。

例如

產生排他鎖的SQL語句如下:select * from ad_plan for update;

更新鎖 U鎖,在修改操作的初始化階段用來鎖定可能要被修改的資源,這樣可以避免使用共享鎖造成的死鎖現象。

因為當使用共享鎖時,修改資料的操作分為兩步:

  1. 首先獲得一個共享鎖,讀取資料,
  2. 然後將共享鎖升級為排他鎖,再執行修改操作。 這樣如果有兩個或多個事務同時對一個事務申請了共享鎖,在修改資料時,這些事務都要將共享鎖升級為排他鎖。這時,這些事務都不會釋放共享鎖,而是一直等待對方釋放,這樣就造成了死鎖。 如果一個數據在修改前直接申請更新鎖,在資料修改時再升級為排他鎖,就可以避免死鎖。

性質

  1. 用來預定要對此頁施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;
  2. 當被讀取的頁要被更新時,則升級為X鎖;
  3. U鎖一直到事務結束時才能被釋放。

悲觀鎖按作用範圍劃分為:行鎖、表鎖。

行鎖(鎖的作用範圍是行級別) 表鎖(鎖的作用範圍是整張表)

資料庫能夠確定那些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖。

舉個例子,一個使用者表user,有主鍵id和使用者生日birthday。 當你使用update … where id=?這樣的語句時,資料庫明確知道會影響哪一行,它就會使用行鎖; 當你使用update … where birthday=?這樣的的語句時,因為事先不知道會影響哪些行就可能會使用表鎖。

樂觀鎖(Optimistic Lock)

顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以,不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有更新這個資料,可以使用版本號等機制。

樂觀鎖( Optimistic Locking ): 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。 悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。

樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。

樂觀鎖的實現方式

  1. 版本號(version)

版本號(記為version):就是給資料增加一個版本標識,在資料庫上就是表中增加一個version欄位,每次更新把這個欄位加1,讀取資料的時候把version讀出來,更新的時候比較version,如果還是開始讀取的version就可以更新了,如果現在的version比老的version大,說明有其他事務更新了該資料,並增加了版本號,這時候得到一個無法更新的通知,使用者自行根據這個通知來決定怎麼處理,比如重新開始一遍。這裡的關鍵是判斷version和更新兩個動作需要作為一個原子單元執行,否則在你判斷可以更新以後正式更新之前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務做的更新,造成第二類丟失更新,所以你可以使用update … where … and version=”old version”這樣的語句,根據返回結果是0還是非0來得到通知,如果是0說明更新沒有成功,因為version被改了,如果返回非0說明更新成功。

  1. 時間戳(使用資料庫伺服器的時間戳)

時間戳(timestamp):和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用資料庫伺服器的時間戳不能是業務系統的時間。

  1. 欄位

使用欄位做版本控制資訊,只有欄位在修改過程中沒變化才會執行更新。

樂觀鎖幾種方式的區別

新系統設計可以使用version方式和timestamp方式,需要增加欄位,應用範圍是整條資料,不論那個欄位修改都會更新version,也就是說兩個事務更新同一條記錄的兩個不相關欄位也是互斥的,不能同步進行。舊系統不能修改資料庫表結構的時候使用資料欄位作為版本控制資訊,不需要新增欄位,待更新欄位方式只要其他事務修改的欄位和當前事務修改的欄位沒有重疊就可以同步進行,併發性更高。

併發控制會造成活鎖和死鎖,就像作業系統那樣,會因為互相等待而導致。

活鎖

定義:指的是T1封鎖了資料R,T2同時也請求封鎖資料R,T3也請求封鎖資料R,當T1釋放了鎖之後,T3會鎖住R,T4也請求封鎖R,則T2就會一直等待下去。 解決方法:採用“先來先服務”策略可以避免。

死鎖

定義:就是我等你,你又等我,雙方就會一直等待下去。比如:T1封鎖了資料R1,正請求對R2封鎖,而T2封住了R2,正請求封鎖R1,這樣就會導致死鎖,死鎖這種沒有完全解決的方法,只能儘量預防。

預防方法:

  1. 一次封鎖法,指的是一次性把所需要的資料全部封鎖住,但是這樣會擴大了封鎖的範圍,降低系統的併發度;
  2. 順序封鎖法,指的是事先對資料物件指定一個封鎖順序,要對資料進行封鎖,只能按照規定的順序來封鎖,但是這個一般不大可能的。

系統判定死鎖的方法:

  1. 超時法:如果某個事物的等待時間超過指定時限,則判定為出現死鎖;
  2. 等待圖法:如果事務等待圖中出現了迴路,則判斷出現了死鎖。

對於解決死鎖的方法,只能是撤銷一個處理死鎖代價最小的事務,釋放此事務持有的所有鎖,同時對撤銷的事務所執行的資料修改操作必須加以恢復。