髒讀、不可重複讀 共享鎖、悲觀鎖 和 事務五種隔離級別

1、髒讀、不可重複讀、幻讀程序員

一、髒讀:髒讀就是指當一個事務正在訪問數據,而且對數據進行了修改,而這種修改尚未提交到數據庫中,這時,另一個事務也訪問這個數據,而後使用了這個數據。
例如:
  張三的工資爲5000,事務A中把他的工資改成8000,但事務A還沒有提交。
  與此同時,
  事務B正在讀取張三的工資,讀取到張三的工資爲8000。
  隨後,
  事務A發生異常,而回滾了事務。張三的工資又回滾爲5000。
  最後,
  事務B讀取到的張三工資爲8000的數據即爲髒數據,事務B作了一次髒讀。sql

二、不可重複讀:是指在一個事務內,屢次讀同一數據。在這個事務尚未結束時,另一個事務也訪問該同一數據。那麼,在第一個事務中的兩次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的,所以稱爲是不可重複讀。
例如:
  在事務A中,讀取到張三的工資爲5000,操做沒有完成,事務還沒提交。
  與此同時,
  事務B把張三的工資改成8000,並提交了事務。
  隨後,
  在事務A中,再次讀取張三的工資,此時工資變爲8000。在一個事務中先後兩次讀取的結果並不致,致使了不可重複讀。數據庫

三、幻讀:是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的所有數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,之後就會發生操做第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺同樣。
例如:
  目前工資爲5000的員工有10人,事務A讀取全部工資爲5000的人數爲10人。
  此時,
  事務B插入一條工資也爲5000的記錄。
  這是,事務A再次讀取工資爲5000的員工,記錄爲11人。此時產生了幻讀。c#

四、提醒
不可重複讀的重點是修改:
一樣的條件,你讀取過的數據,再次讀取出來發現值不同了
幻讀的重點在於新增或者刪除:
一樣的條件,第 1 次和第 2 次讀出來的記錄數不同

2、獨佔鎖、共享鎖、更新鎖,樂觀鎖、悲觀鎖安全

一、鎖的兩種分類方式session

(1)從數據庫系統的角度來看,鎖分爲如下三種類型:併發

 獨佔鎖(Exclusive Lock)
      獨佔鎖鎖定的資源只容許進行鎖定操做的程序使用,其它任何對它的操做均不會被接受。執行數據更新命令,即INSERT、 UPDATE 或DELETE 命令時,SQL Server 會自動使用獨佔鎖。但當對象上有其它鎖存在時,沒法對其加獨佔鎖。獨佔鎖一直到事務結束才能被釋放。
 共享鎖(Shared Lock)
      共享鎖鎖定的資源能夠被其它用戶讀取,但其它用戶不能修改它。在SELECT 命令執行時,SQL Server 一般會對對象進行共享鎖鎖定。一般加共享鎖的數據頁被讀取完畢後,共享鎖就會當即被釋放。
 更新鎖(Update Lock)
      更新鎖是爲了防止死鎖而設立的。當SQL Server 準備更新數據時,它首先對數據對象做更新鎖鎖定,這樣數據將不能被修改,但能夠讀取。等到SQL Server 肯定要進行更新數據操做時,它會自動將更新鎖換爲獨佔鎖。但當對象上有其它鎖存在時,沒法對其做更新鎖鎖定。 框架

(2)從程序員的角度看,鎖分爲如下兩種類型:性能

悲觀鎖(Pessimistic Lock)
      悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。 測試

樂觀鎖(Optimistic Lock)
      相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。
      而樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本( Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。

二、數據庫中如何使用鎖

首先從悲觀鎖開始說。在SqlServer等其他不少數據庫中,數據的鎖定一般採用頁級鎖的方式,也就是說對一張表內的數據是一種串行化的更新插入機制,在任什麼時候間同一張表只會插1條數據,別的想插入的數據要等到這一條數據插完之後才能依次插入。帶來的後果就是性能的下降,在多用戶併發訪問的時候,當對一張表進行頻繁操做時,會發現響應效率很低,數據庫常常處於一種假死狀態。而Oracle用的是行級鎖,只是對想鎖定的數據才進行鎖定,其他的數據不相干,因此在對Oracle表中併發插數據的時候,基本上不會有任何影響。

注:對於悲觀鎖是針對併發的可能性比較大,而通常在咱們的應用中用樂觀鎖足以。

Oracle的悲觀鎖須要利用一條現有的鏈接,分紅兩種方式,從SQL語句的區別來看,就是一種是for update,一種是for update nowait的形式。

好比咱們看一個例子。首先創建測試用的數據庫表:

CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept

這裏咱們利用了Oracle的Sample的scott用戶的表,把數據copy到咱們的test表中。

(1)for update 形式介紹

而後咱們看一下for update鎖定方式。咱們執行以下的select for update語句:

select * from test where id = 10 for update

經過這條檢索語句鎖定之後,再開另一個sql*plus窗口進行操做,再把上面這條sql語句執行一便,你會發現sqlplus好像死在那裏了,好像檢索不到數據的樣子,可是也不返回任何結果,就屬於卡在那裏的感受。這個時候是什麼緣由呢,就是一開始的第一個Session中的select for update語句把數據鎖定住了。因爲這裏鎖定的機制是wait的狀態(只要不表示nowait那就是wait),因此第二個Session(也就是卡住的那個sql*plus)中當前這個檢索就處於等待狀態。當第一個session最後commit或者rollback以後,第二個session中的檢索結果就是自動跳出來,而且也把數據鎖定住。

不過若是你第二個session中你的檢索語句以下所示:select * from test where id = 10,也就是沒有for update這種鎖定數據的語句的話,就不會形成阻塞了。

(2)for update nowait 形式介紹

另一種狀況,就是當數據庫數據被鎖定的時候,也就是執行剛纔for update那條sql之後,咱們在另一個session中執行for update nowait後又是什麼樣呢。
好比以下的sql語句:

select * from test where id = 10 for update nowait

因爲這條語句中是制定採用nowait方式來進行檢索,因此當發現數據被別的session鎖定中的時候,就會迅速返回ORA-00054錯誤,內容是資源正忙, 但指定以 NOWAIT 方式獲取資源。因此在程序中咱們能夠採用nowait方式迅速判斷當前數據是否被鎖定中,若是鎖定中的話,就要採起相應的業務措施進行處理。

那這裏另一個問題,就是當咱們鎖定住數據的時候,咱們對數據進行更新和刪除的話會是什麼樣呢。

好比一樣,咱們讓第一個Session鎖定住id=10的那條數據,咱們在第二個session中執行以下語句:

update test set value=2 where id = 10

這個時候咱們發現update語句就好像select for update語句同樣也停住卡在這裏,當你第一個session放開鎖定之後update才能正常運行。當你update運行後,數據又被你update 語句鎖定住了,這個時候只要你update後尚未commit,別的session照樣不能對數據進行鎖定更新等等。

總之,Oracle中的悲觀鎖就是利用Oracle的Connection對數據進行鎖定。在Oracle中,用這種行級鎖帶來的性能損失是很小的,只是要注意程序邏輯,不要給你一不當心搞成死鎖了就好。並且因爲數據的及時鎖定,在數據提交時候就不呼出現衝突,能夠省去不少惱人的數據衝突處理。缺點就是你必需要始終有一條數據庫鏈接,就是說在整個鎖定到最後放開鎖的過程當中,你的數據庫聯接要始終保持住。

與悲觀鎖相對的,咱們有了樂觀鎖。樂觀鎖一開始也說了,就是一開始假設不會形成數據衝突,在最後提交的時候再進行數據衝突檢測。

在樂觀鎖中,咱們有3種經常使用的作法來實現:

a. 在數據取得的時候把整個數據都copy到應用中,在進行提交的時候比對當前數據庫中的數據和開始的時候更新前取得的數據。
    當發現兩個數據如出一轍之後,就表示沒有衝突能夠提交,不然則是併發衝突,須要去用業務邏輯進行解決。

b. 樂觀鎖的作法就是採用版本戳,這個在Hibernate中獲得了使用。
    採用版本戳的話,首先須要在你有樂觀鎖的數據庫table上創建一個新的column,好比爲number型,當你數據每更新一次的時候,版本數就會往上增長1。
    好比一樣有2個session一樣對某條數據進行操做。二者都取到當前的數據的版本號爲1,當第一個session進行數據更新後,在提交的時候查看到當前數據的版本還爲1,和本身一開始取到的版本相同。就正式提交,而後把版本號增長1,這個時候當前數據的版本爲2。當第二個session也更新了數據提交的時候,發現數據庫中版本爲2,和一開始這個session取到的版本號不一致,就知作別人更新過此條數據,這個時候再進行業務處理,好比整個Transaction都Rollback等等操做。
    在用版本戳的時候,能夠在應用程序側使用版本戳的驗證,也能夠在數據庫側採用Trigger(觸發器)來進行驗證。不過數據庫的Trigger的性能開銷仍是比較的大,因此能在應用側進行驗證的話仍是推薦不用Trigger。

c. 第三種作法和第二種作法有點相似,就是也新增一個Table的Column,不過此次這個column是採用timestamp型,存儲數據最後更新的時間。
    在Oracle9i之後能夠採用新的數據類型,也就是timestamp with time zone類型來作時間戳。這種Timestamp的數據精度在Oracle的時間類型中是最高的,精確到微秒(還沒與到納秒的級別),通常來講,加上數據庫處理時間和人的思考動做時間,微秒級別是很是很是夠了,其實只要精確到毫秒甚至秒都應該沒有什麼問題。
    和剛纔的版本戳相似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和本身更新前取到的時間戳進行對比,若是一致則OK,不然就是版本衝突。若是不想把代碼寫在程序中或者因爲別的緣由沒法把代碼寫在現有的程序中,也能夠把這個時間戳樂觀鎖邏輯寫在Trigger或者存儲過程當中。

 

3、事務五種隔離級別

Isolation 屬性一共支持五種事務設置,具體介紹以下:
(1)DEFAULT
  使用數據庫設置的隔離級別(默認),由DBA 默認的設置來決定隔離級別。
(2)READ_UNCOMMITTED
  這是事務最低的隔離級別,它充許別外一個事務能夠看到這個事務未提交的數據。
  會出現髒讀、不可重複讀、幻讀 (隔離級別最低,併發性能高)。
(3)READ_COMMITTED
  保證一個事務修改的數據提交後才能被另一個事務讀取。另一個事務不能讀取該事務未提交的數據。
  能夠避免髒讀,但會出現不可重複讀、幻讀問題(鎖定正在讀取的行)。
(4)REPEATABLE_READ
  能夠防止髒讀、不可重複讀,但會出幻讀(鎖定所讀取的全部行)。
(5)SERIALIZABLE
  這是花費最高代價可是最可靠的事務隔離級別,事務被處理爲順序執行。
  保證全部的狀況不會發生(鎖表)。

 

4、c# 事務原理

企業級的數據庫每一秒鐘均可能應付成千上萬的併發訪問,於是帶來了併發控制的問題。由數據庫理論可知,因爲併發訪問,在不可預料的時刻可能引起以下幾個能夠預料的問題:
  髒讀:包含未提交數據的讀取。例如,事務1 更改了某行。事務2 在事務1 提交更改以前讀取已更改的行。若是事務1 回滾更改,則事務2 便讀取了邏輯上從未存在過的行。
  不可重複讀取:當某個事務不止一次讀取同一行,而且一個單獨的事務在兩次(或屢次)讀取之間修改該行時,由於在同一個事務內的屢次讀取之間修改了該行,因此每次讀取都生成不一樣值,從而引起不一致問題。
  幻象:經過一個任務,在之前由另外一個還沒有提交其事務的任務讀取的行的範圍中插入新行或刪除現有行。帶有未提交事務的任務因爲該範圍中行數的更改而沒法重複其原始讀取。

如你所想,這些狀況發生的根本緣由都是由於在併發訪問的時候,沒有一個機制避免交叉存取所形成的。而隔離級別的設置,正是爲了不這些狀況的發生。事務準備接受不一致數據的級別稱爲隔離級別。隔離級別是一個事務必須與其它事務進行隔離的程度。較低的隔離級別能夠增長併發,但代價是下降數據的正確性。相反,較高的隔離級別能夠確保數據的正確性,但可能對併發產生負面影響。

根據隔離級別的不一樣,DBMS爲並行訪問提供不一樣的互斥保證。在SQL Server數據庫中,提供四種隔離級別:未提交讀、提交讀、可重複讀、可串行讀。這四種隔離級別能夠不一樣程度地保證併發的數據完整性: 

隔離級別 髒 讀 不可重複讀取 幻 像
未提交讀
提交讀
可重複讀
可串行讀

 

 

 

 

能夠看出,「可串行讀」提供了最高級別的隔離,這時併發事務的執行結果將與串行執行的徹底一致。如前所述,最高級別的隔離也就意味着最低程度的併發,所以,在此隔離級別下,數據庫的服務效率事實上是比較低的。儘管可串行性對於事務確保數據庫中的數據在全部時間內的正確性至關重要,然而許多事務並不老是要求徹底的隔離。例如,多個做者工做於同一本書的不一樣章節。新章節能夠在任意時候提交到項目中。可是,對於已經編輯過的章節,沒有編輯人員的批准,做者不能對此章節進行任何更改。這樣,儘管有未編輯的新章節,但編輯人員仍能夠確保在任意時間該書籍項目的正確性。編輯人員能夠查看之前編輯的章節以及最近提交的章節。這樣,其它的幾種隔離級別也有其存在的意義。

在.net框架中,事務的隔離級別是由枚舉System.Data.IsolationLevel所定義的:

[Flags]
[Serializable]
public enum IsolationLevel

其成員及相應的含義以下:

成 員 含 義
Chaos 沒法改寫隔離級別更高的事務中的掛起的更改。
ReadCommitted 在正在讀取數據時保持共享鎖,以免髒讀,可是在事務結束以前能夠更改數據,從而致使不可重複的讀取或幻像數據。
ReadUncommitted 能夠進行髒讀,意思是說,不發佈共享鎖,也不接受獨佔鎖。
RepeatableRead 在查詢中使用的全部數據上放置鎖,以防止其餘用戶更新這些數據。防止不可重複的讀取,可是仍能夠有幻像行。
Serializable 在DataSet上放置範圍鎖,以防止在事務完成以前由其餘用戶更新行或向數據集中插入行。
Unspecified 正在使用與指定隔離級別不一樣的隔離級別,可是沒法肯定該級別。

顯而意見,數據庫的四個隔離級別在這裏都有映射。

默認的狀況下,SQL Server使用ReadCommitted(提交讀)隔離級別。

關於隔離級別的最後一點就是若是你在事務執行的過程當中改變了隔離級別,那麼後面的命名都在最新的隔離級別下執行——隔離級別的改變是當即生效的。有了這一點,你能夠在你的事務中更靈活地使用隔離級別從而達到更高的效率和併發安全性。

相關文章
相關標籤/搜索