本文轉自 http://singo107.iteye.com/blog/1175084
同時參考 http://blog.csdn.net/kaoa000/article/details/15814919
同時參考 http://www.hollischuang.com/archives/934
數據庫有四種隔離級別,分別為 Read uncommitted,Read committed,Repeatable read,Serizable。 講解圍繞事務並發。
√:會出現 ×:不會出現
隔離級別 |
臟讀 |
不可重復讀 |
幻讀 |
Read uncommitted |
√ |
√ |
√ |
Read committed |
× |
√ |
√ |
Repeatable read |
× |
× |
√ |
Serializable |
× |
× |
× |
1、Read uncommitted 讀未提交
公司發工資了,領導把5000元打到tom的卡上,但是還未提交事務,這時tom查看自己的銀行卡,發現自己多了工資5000元,心里想着為什么這次工資少了,但是這時老板發現給tom算錯工資了,是10000元,於是事務回滾,將工資修改為10000元,tom再次查銀行卡發現自己的工資是10000元,心里總算解悶了。
當隔離級別設置為Read uncommitted 時,容易出現臟讀。
2、Read committed 讀提交
tom去超市購物,結賬時系統讀到卡里有10000元,而此時tom的老婆正在網上轉賬,把tom卡里的10000元轉到了另一賬戶,並在tom前提交了事務,此時系統檢查到tom的工資卡里已經沒有錢了,tom非常納悶,明明卡里有錢...
當隔離級別設置為Read committed時,避免了臟讀,容易出現不可重復讀。大多數數據庫的隔離級別設置為Read committed,如Sql Server,Oracle。怎樣避免不可重復讀,看下一個隔離級別。
3、Repeatable read
當數據庫的隔離級別設置為Repeatable read時,可以避免不可重復讀,即tom拿着工資卡去消費,系統一旦讀工資卡,tom的老婆就不能讀工資卡了,tom的老婆也不能在此時轉賬。Repeatable read避免了不可重復讀,但是有可能出現幻讀。
tom平時還挺節儉,tom的老婆在銀行部門,她經常通過銀行系統查看tom的消費記錄。有一天,她查到tom的卡消費是80元,但是tom此時正在外面胡吃海喝,消費了1000元,tom的老婆打印賬單時顯示tom的消費記錄是1080元,tom的老婆很詫異,以為出現了幻覺,幻讀就這樣產生了。
注: MySQL的隔離級別就是Repeatable read。
4、Serializable 序列化
序列化是最高的事務隔離級別,同時花費代價也很高,性能很低,一般很少使用,在該級別下,事務順序執行,不僅避免了臟讀,不可重復讀,而且避免了幻讀。
5、多個事務並發運行時的並發問題
第一類丟失更新:撤銷一個事務時,把其他事務已經提交的更新數據覆蓋。
臟讀:一個事務讀到另一個事務未提交的數據。
虛讀:一個事務讀到另一個事務已提交的新插入的數據。
不可重復讀:一個事務讀到另一個事務已提交的更新的數據。
第二類丟失更新:這是不可重復讀中的特例,一個事務覆蓋另一個事務已提交的更新數據。
並發運行的兩個事務導致第二類丟失更新:
時間 | 取款事務 | 支票轉賬事務 |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢賬戶余額為1000元 | |
T4 | 查詢賬戶余額為1000元 | |
T5 | 取出100,把存款余額改為900元 | |
T6 | 提交事務 | |
T7 | 匯入100元,把存款余額改為1100元 | |
T8 | 提交事務 | |
設置隔離級別的原則
對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設置為Read committed,能夠避免臟讀,而且有較好的並發性能,盡管它能導致不可重復讀、幻讀、第二類丟失更新這些並發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。
樂觀鎖和悲觀鎖是並發控制主要采用的技術手段。數據庫、memcache、hibernate、tair等都有類似的概念。
悲觀鎖
在關系數據庫管理系統里,悲觀鎖是一種並發控制的方式,它可以阻止一個事務以影響其他用戶的方式來修改數據。如果一個事務執行的操作中某行數據應用了鎖,只有等這個事務把鎖釋放,其他事務才能執行與改鎖沖突的操作。
悲觀鎖主要用於數據爭用激烈的環境,以及發生沖突時使用鎖保護數據的成本低於回滾事務的成本的環境中。
數據庫中悲觀鎖的流程如下:
- 在對任意記錄修改前,先嘗試為該記錄加上排他鎖
- 如果加鎖失敗,說明該記錄已經加上排他鎖或者正在被修改,那么當前查詢可能要等待或者拋出異常
- 如果成功加鎖,那么就可以對該記錄做修改,事務完成后就會解鎖了
- 期間如果有其他隊該記錄加鎖或修改,都會等待解鎖或直接拋出異常
//0.開始事務 begin;/begin work;/start transaction; (三者選一就可以) //1.查詢出商品信息 select status from t_goods where id=1 for update; 開啟了排他鎖 //2.根據商品信息生成訂單 insert into t_orders (id,goods_id) values (null,1); //3.修改商品status為2 update t_goods set status=2; //4.提交事務 commit;/commit work;
上面的查詢語句中,我們使用了select…for update
的方式,這樣就通過開啟排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id為1的 那條數據就被我們鎖定了,其它的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。
優點與不足:
悲觀鎖采用的是“先取鎖再訪問”的保守策略,為數據處理的安全提供了保障。但是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增加產生死鎖機會;另外,在只讀事務處理中由於不會產生沖突,所以不需要加鎖,這樣只能增加系統負載;還有會降低並發性,一個事務如果鎖定了某行數據,其他事務就必須等待那行數據被處理完才能處理那行數據。
樂觀鎖
在關系數據庫里,樂觀鎖是一種並發控制的方法,假設認為數據一般情況下不會造成沖突,所以在數據進行更新提交的時候才正式對數據的沖突與否進行檢測,如果發現沖突了,則返回用戶錯誤信息,讓用戶決定如何去做,樂觀鎖不會使用數據庫提供的鎖機制,實現樂觀鎖的方式就是記錄數據版本。實現數據版本有使用版本號和時間戳兩種方式。
數據版本,是數據增加的一個版本標識。當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當我們提交數據更新的時候,判斷數據庫表對應的當前版本信息與第一次取出來的版本標識進行比對,如果數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期數據。
使用版本號實現樂觀鎖
使用版本號時,可以在數據初始化時指定一個版本號,每次對數據的更新操作都對版本號執行+1操作,並判斷當前版本號是不是最新版本號。
1.查詢出商品信息 select (status,status,version) from t_goods where id=#{id} 2.根據商品信息生成訂單 3.修改商品status為2 update t_goods set status=2,version=version+1
where id=#{id} and version=#{version};
優點與不足
樂觀鎖相信數據之間的競爭是非常小的,因此盡可能直接做下去,知道提交的時候才去鎖定,所以不會產生任何鎖和死鎖。但如果直接簡單這么做,還是會遇到問題,例如兩個事務都讀取了數據庫的某一行,經過修改后又寫回了數據庫,這時就會遇到問題。