關於數據庫事務、鎖的理解與整理

mysql select是否會鎖表?

對於myisam的表select 是會鎖定表的 ,會導致其他操作掛起,處於等待狀態。
對於innodb的表select 是不會鎖表的。其實這裏使用到了快照。快照這裏不作討論。

 

數據庫鎖分類

一般可以分爲兩類,一個是悲觀鎖,一個是樂觀鎖,悲觀鎖一般就是我們通常說的數據庫鎖機制,樂觀鎖一般是指用戶自己實現的一種鎖機制,比如hibernate實現的樂觀鎖甚至編程語言也有樂觀鎖的思想的應用。

悲觀鎖:顧名思義,就是很悲觀,它對於數據被外界修改持保守態度,認爲數據隨時會修改,所以整個數據處理中需要將數據加鎖。悲觀鎖一般都是依靠關係數據庫提供的鎖機制,事實上關係數據庫中的行鎖,表鎖不論是讀寫鎖都是悲觀鎖。

悲觀鎖按照使用性質劃分:

  • 共享鎖(Share locks簡記爲S鎖):也稱讀鎖,事務A對對象T加s鎖,其他事務也只能對T加S,多個事務可以同時讀,但不能有寫操作,直到A釋放S鎖。

  • 排它鎖(Exclusivelocks簡記爲X鎖):也稱寫鎖,事務A對對象T加X鎖以後,其他事務不能對T加任何鎖,只有事務A可以讀寫對象T直到A釋放X鎖。

  • 更新鎖(簡記爲U鎖):用來預定要對此對象施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的對象將要被更新時,則升級爲X鎖,主要是用來防止死鎖的。因爲使用共享鎖時修改數據的操作分爲兩步,首先獲得一個共享鎖,讀取數據,然後將共享鎖升級爲排它鎖,然後再執行修改操作。這樣如果同時有兩個或多個事務同時對一個對象申請了共享鎖,在修改數據的時候,這些事務都要將共享鎖升級爲排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖如果一個數據在修改前直接申請更新鎖,在數據修改的時候再升級爲排它鎖,就可以避免死鎖。

悲觀鎖按照作用範圍劃分:

  • 行鎖:鎖的作用範圍是行級別,數據庫能夠確定那些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖。舉個例子,一個用戶表user,有主鍵id和用戶生日birthday當你使用update … where id=?這樣的語句數據庫明確知道會影響哪一行,它就會使用行鎖,當你使用update … where birthday=?這樣的的語句的時候因爲事先不知道會影響哪些行就可能會使用表鎖。
  • 表鎖:鎖的作用範圍是整張表。

樂觀鎖:顧名思義,就是很樂觀,每次自己操作數據的時候認爲沒有人回來修改它,所以不去加鎖,但是在更新的時候會去判斷在此期間數據有沒有被修改,需要用戶自己去實現。既然都有數據庫提供的悲觀鎖可以方便使用爲什麼要使用樂觀鎖呢?對於讀操作遠多於寫操作的時候,大多數都是讀取,這時候一個更新操作加鎖會阻塞所有讀取,降低了吞吐量。最後還要釋放鎖,鎖是需要一些開銷的,我們只要想辦法解決極少量的更新操作的同步問題。換句話說,如果是讀寫比例差距不是非常大或者你的系統沒有響應不及時,吞吐量瓶頸問題,那就不要去使用樂觀鎖,它增加了複雜度,也帶來了額外的風險。

樂觀鎖實現方式:

  • 版本號(記爲version):就是給數據增加一個版本標識,在數據庫上就是表中增加一個version字段,每次更新把這個字段加1,讀取數據的時候把version讀出來,更新的時候比較version,如果還是開始讀取的version就可以更新了,如果現在的version比老的version大,說明有其他事務更新了該數據,並增加了版本號,這時候得到一個無法更新的通知,用戶自行根據這個通知來決定怎麼處理,比如重新開始一遍。這裏的關鍵是判斷version和更新兩個動作需要作爲一個原子單元執行,否則在你判斷可以更新以後正式更新之前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務做的更新,造成第二類丟失更新,所以你可以使用update … where … and version=”old version”這樣的語句,根據返回結果是0還是非0來得到通知,如果是0說明更新沒有成功,因爲version被改了,如果返回非0說明更新成功。
  • 時間戳(timestamp):和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用數據庫服務器的時間戳不能是業務系統的時間。
  • 待更新字段:和版本號方式相似,只是不增加額外字段,直接使用有效數據字段做版本控制信息,因爲有時候我們可能無法改變舊系統的數據庫表結構。假設有個待更新字段叫count,先去讀取這個count,更新的時候去比較數據庫中count的值是不是我期望的值(即開始讀的值),如果是就把我修改的count的值更新到該字段,否則更新失敗。java的基本類型的原子類型對象如AtomicInteger就是這種思想。
  • 所有字段:和待更新字段類似,只是使用所有字段做版本控制信息,只有所有字段都沒變化纔會執行更新。

    樂觀鎖幾種方式的區別:

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

什麼是事務(Transaction)?
是指作爲單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。 事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合爲一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。一個邏輯工作單元要成爲事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。事務是數據庫運行中的一個邏輯工作單位,由DBMS中的事務管理子系統負責事務的處理。

舉個例子加深一下理解:同一個銀行轉賬,A轉1000塊錢給B,這裏存在兩個操作,一個是A賬戶扣款1000元,兩一個操作是B賬戶增加1000元,兩者就構成了轉賬這個事務。

兩個操作都成功,A賬戶扣款1000元,B賬戶增加1000元,事務成功
兩個操作都失敗,A賬戶和B賬戶金額都沒變,事務失敗
最後思考一下,怎麼樣會出現A賬戶扣款1000元,B賬戶金額不變?如果你是把兩個操作放在一個事務裏面,並且是數據庫提供的內在事務支持,那就不會有問題,但是開發人員把兩個操作放在兩個事務裏面,而第二個事務失敗就會出現中間狀態。現實中自己實現的分佈式事務處理不當也會出現中間狀態,這並不是事務的錯,事務本身就是規定不會出現中間狀態,是事務實現者做出來的方案有問題。

事務的4個特性
原子性(Atomic):事務必須是原子工作單元;對於其數據修改,要麼全都執行,要麼全都不執行。通常,與某個事務關聯的操作具有共同的目標,並且是相互依賴的。如果系統只執行這些操作的一個子集,則可能會破壞事務的總體目標。原子性消除了系統處理操作子集的可能性。

一致性(Consistency):事務的一致性指的是在一個事務執行之前和執行之後數據庫都必須處於一致性狀態。這種特性稱爲事務的一致性。假如數據庫的狀態滿足所有的完整性約束,就說該數據庫是一致的。

隔離性(Isolation):由併發事務所作的修改必須與任何其它併發事務所作的修改隔離。事務查看數據時數據所處的狀態,到底是另一個事務執行之前的狀態還是中間某個狀態,相互之間存在什麼影響,是可以通過隔離級別的設置來控制的。

持久性(Durability):事務結束後,事務處理的結果必須能夠得到固化,即寫入數據庫文件中即使機器宕機數據也不會丟失,它對於系統的影響是永久性的。

事務併發控制
我們從另外一個方向來說說,如果不對事務進行併發控制,我們看看數據庫併發操作是會有那些異常情形,有些使我們可以接受的,有些是不能接受的,注意這裏的異常就是特定語境下的,並不一定就是錯誤什麼的。假設有一個order表,有個字段叫count,作爲計數用,當前值爲100

第一類丟失更新(Update Lost):此種更新丟失是因爲回滾的原因,所以也叫回滾丟失。此時兩個事務同時更新count,兩個事務都讀取到100,事務一更新成功並提交,count=100+1=101,事務二出於某種原因更新失敗了,然後回滾,事務二就把count還原爲它一開始讀到的100,此時事務一的更新就這樣丟失了。

髒讀(Dirty Read):此種異常時因爲一個事務讀取了另一個事務修改了但是未提交的數據。舉個例子,事務一更新了count=101,但是沒有提交,事務二此時讀取count,值爲101而不是100,然後事務一出於某種原因回滾了,然後第二個事務讀取的這個值就是噩夢的開始。

不可重複讀(Not Repeatable Read):此種異常是一個事務對同一行數據執行了兩次或更多次查詢,但是卻得到了不同的結果,也就是在一個事務裏面你不能重複(即多次)讀取一行數據,如果你這麼做了,不能保證每次讀取的結果是一樣的,有可能一樣有可能不一樣。造成這個結果是在兩次查詢之間有別的事務對該行數據做了更新操作。舉個例子,事務一先查詢了count,值爲100,此時事務二更新了count=101,事務一再次讀取count,值就會變成101,兩次讀取結果不一樣。

第二類丟失更新(Second Update Lost):此種更新丟失是因爲更新被其他事務給覆蓋了,也可以叫覆蓋丟失。舉個例子,兩個事務同時更新count,都讀取100這個初始值,事務一先更新成功並提交,count=100+1=101,事務二後更新成功並提交,count=100+1=101,由於事務二count還是從100開始增加,事務一的更新就這樣丟失了。

幻讀(Phantom Read):幻讀和不可重複讀有點像,只是針對的不是數據的值而是數據的數量。此種異常是一個事務在兩次查詢的過程中數據的數量不同,讓人以爲發生幻覺,幻讀大概就是這麼得來的吧。舉個例子,事務一查詢order表有多少條記錄,事務二新增了一條記錄,然後事務一查了一下order表有多少記錄,發現和第一次不一樣,這就是幻讀。

數據庫事務隔離級別
看到上面提到的幾種問題,你可能會想,我擦,這麼多坑怎麼辦啊。其實上面幾種情況並不是一定都要避免的,具體看你的業務要求,包括你數據庫的負載都會影響你的決定。不知道大家發現沒有,上面各種異常情況都是多個事務之間相互影響造成的,這說明兩個事務之間需要某種方式將他們從某種程度上分開,降低直至避免相互影響。這時候數據庫事務隔離級別就粉墨登場了,而數據庫的隔離級別實現一般是通過數據庫鎖實現的。

讀未提交(Read Uncommitted):該隔離級別指即使一個事務的更新語句沒有提交,但是別的事務可以讀到這個改變,幾種異常情況都可能出現。極易出錯,沒有安全性可言,基本不會使用。

讀已提交(Read Committed):該隔離級別指一個事務只能看到其他事務的已經提交的更新,看不到未提交的更新,消除了髒讀和第一類丟失更新,這是大多數數據庫的默認隔離級別,如Oracle,Sqlserver。

可重複讀(Repeatable Read):該隔離級別指一個事務中進行兩次或多次同樣的對於數據內容的查詢,得到的結果是一樣的,但不保證對於數據條數的查詢是一樣的,只要存在讀改行數據就禁止寫,消除了不可重複讀和第二類更新丟失,這是Mysql數據庫的默認隔離級別。

串行化(Serializable):意思是說這個事務執行的時候不允許別的事務併發執行.完全串行化的讀,只要存在讀就禁止寫,但可以同時讀,消除了幻讀。這是事務隔離的最高級別,雖然最安全最省心,但是效率太低,一般不會用。
 

級別\異常 第一類更新丟失 髒讀 不可重複讀 第二類丟失更新 幻讀
讀未提交 Y Y Y Y Y
讀已提交 N N Y Y Y
可重複讀 N N N N Y
串行化 N N N N N

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章