數據庫的事務以及事務隔離級別

學習數據庫的時候常常會接觸到事務, ACID等概念,那麼到底什麼是數據庫的事務,數據庫事務又具有哪些特點,和ACID有怎樣的關係,事務的隔離級別又是做什麼的呢?。

事務及其四大特性?

事務(Transaction):訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit),它通常由高級數據庫操縱語言或編程語言(如SQL,C++或Java)書寫的用戶程序的執行所引起。當在數據庫中更改數據成功時,在事務中更改的數據便會提交,不再改變。否則,事務就取消或者回滾,更改無效。

舉個例子來說,張三給李四轉了1000元錢,那麼在數據庫操作時,就要先把張三的賬戶減去1000元,再把李四的賬戶加上1000元,兩部分操作放在一起,纔是一個完整的轉賬過程,也可稱之爲事務。

(1)原子性(Atomicity)

原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇博客介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。

就像你買東西要麼交錢收貨一起都執行,要麼要是發不出貨,就退錢。

(2) 一致性(Consistency)

一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態

拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

打個比方,你買東西這個事情,是不影響其他人的。

(3)隔離性(Isolation)

隔離性是當多個用戶併發訪問數據庫時,比如操作同一張表時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離

即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後纔開始,這樣每個事務都感覺不到有其他事務在併發地執行

關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。

(4)持久性(Durability)

持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。

例如我們在使用JDBC操作數據庫時,在提交事務方法後,提示用戶事務操作完成,當我們程序執行完成直到看到提示後,就可以認定事務已經正確提交,即使這時候數據庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是數據庫因爲故障而沒有執行事務的重大錯誤。

打個比方,你買東西的時候需要記錄在賬本上,即使老闆忘記了那也有據可查。

InnoDB引擎的事務實現

InnoDB是mysql的一個存儲引擎,大部分人對mysql都比較熟悉,這裏簡單介紹一下數據庫事務實現的一些基本原理,在本地事務中,服務和資源在事務的包裹下可以看做是一體的。
在這裏插入圖片描述
事務的ACID是通過InnoDB日誌和鎖來保證事務的隔離性是通過數據庫鎖的機制實現的持久性通過redo log(重做日誌)來實現,原子性和一致性通過Undo log(回撤日誌)來實現。Undo Log的原理很簡單,爲了滿足事務的原子性,在操作任何數據之前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲Undo Log)。然後進行數據的修改。如果出現了錯誤或者用戶執行了roll back語句,系統可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態。 和Undo Log相反,Redo Log記錄的是新數據的備份。在事務提交前,只要將RedoLog持久化即可,不需要將數據持久化。當系統崩潰時,雖然數據沒有持久化,但是RedoLog已經持久化。系統可以根據Redo Log的內容,將所有數據恢復到最新的狀態

事務的隔離性及隔離級別

以上介紹完事務的四大特性(簡稱ACID),現在重點來說明下事務的隔離性,當多個線程都開啓事務操作數據庫中的數據時,數據庫系統要能進行隔離操作,以保證各個線程獲取數據的準確性,在介紹數據庫提供的各種隔離級別之前,我們先看看如果不考慮事務的隔離性,會發生的幾種問題:
在這裏插入圖片描述
在這裏插入圖片描述
3、幻讀

一個事務在前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。如果事務中都使用快照讀,那麼就不會產生幻讀現象,但是快照讀和當前讀混用就會產生幻讀。關於快照讀與當前讀可參見《InnoDB對MVCC的實現》

幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就與髒讀不同),所不同的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。

“髒讀”、“不可重複讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。數據庫實現事務隔離的方式,基本上可分爲以下兩種:

  • 一種是在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
  • 另一種是不用加任何鎖,通過一定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取。從用戶的角度來看,好像是數據庫可以提供同一數據的多個版本,因此,這種技術叫做數據多版本併發控制(MultiVersion Concurrency Control,簡稱MVCC或MCC),也經常稱爲多版本數據庫。

數據庫的事務隔離越嚴格,併發副作用越小,但付出的代價也就越大,因爲事務隔離實質上就是使事務在一定程度上 “串行化”進行,這顯然與“併發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重複讀”和“幻讀”並不敏感,可能更關心數據併發訪問的能力。

四種隔離級別

爲了解決“隔離”與“併發”的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別,每個級別的隔離程度不同,允許出現的副作用也不同,應用可以根據自己的業務邏輯要求,通過選擇不同的隔離級別來平衡 “隔離”與“併發”的矛盾。

4個事務隔離級別:

  • Read uncommitted (讀未提交):最低級別,以上問題均無法解決。
  • Read committed (讀已提交):讀已提交,可避免髒讀情況發生。
  • Repeatable Read(可重複讀):確保事務可以多次從一個字段中讀取相同的值,在此事務持續期間,禁止其他事務對此字段的更新,可以避免髒讀和不可重複讀,仍會出現幻讀問題。
  • Serializable (串行化):最嚴格的事務隔離級別,要求所有事務被串行執行,不能併發執行,可避免髒讀、不可重複讀、幻讀情況的發生。

這四種隔離級別,分別有可能產生問題總結如下:
在這裏插入圖片描述

很多人容易搞混不可重複讀和幻讀,確實這兩者有些相似。但不可重複讀重點在於update和delete,而幻讀的重點在於insert。避免不可重複讀需要鎖行(某一行在select操作時,不允許update與delete)就行,避免幻讀則需要鎖表。如果使用鎖機制來實現這兩種隔離級別,在可重複讀中,該sql第一次讀取到數據後,就將這些數據加鎖,其它事務無法修改這些數據,就可以實現可重複讀了。但這種方法卻無法鎖住insert的數據,所以當事務A先前讀取了數據,或者修改了全部數據,事務B還是可以insert數據提交,這時事務A就會發現莫名其妙多了一條之前沒有的數據,幻讀不能通過行鎖來避免,需要Serializable隔離級別 ,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這麼做可以有效的避免幻讀、不可重複讀、髒讀等問題,但會極大的降低數據庫的併發能力。所以說不可重複讀和幻讀最大的區別,就在於如何通過鎖機制來解決他們產生的問題

以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然隔離級別越高,越能保證數據的完整性和統一性,但是執行效率就越低,對併發性能的影響也越大。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況

各類流行的數據庫都實現了一些SQL標準中的事務隔離級別,但是他們的實現也是極其不一樣的。Oracle僅僅實現了RC 和 SERIALIZABLE隔離級別。默認採用RC隔離級別,解決了髒讀。但是允許不可重複讀和幻讀。其SERIALIZABLE則解決了髒讀、不可重複讀、幻讀。MySQL支持全部4個隔離級別,但在具體實現時,有一些特點,比如在一些隔離級別下是採用MVCC一致性讀,但某些情況下又不是。MySQL默認採用RR隔離級別,SQL標準是要求RR解決不可重複讀的問題,但是因爲MySQL通過nex-key lock在RR隔離級別下解決了幻讀的問題。那麼MySQL的SERIALIZABLE是怎麼回事呢?MySQL的SERIALIZABLE採用了經典的實現方式,對讀和寫都加鎖。

查看MySQL數據庫當前事務的隔離級別:

<span style="color:#000000"><code>select @@tx_isolation;
</code></span>
  • 1

在這裏插入圖片描述
在MySQL數據庫中設置事務的隔離級別:

<span style="color:#000000"><code>set  [glogal | session]  transaction isolation level 隔離級別名稱;
set tx_isolation=’隔離級別名稱;’
</code></span>
  • 1
  • 2

在這裏插入圖片描述

互聯網項目中MySQL用什麼事務隔離級別

Mysql默認的事務隔離級別是可重複讀(Repeatable Read),那互聯網項目中Mysql也是用默認隔離級別,不做修改麼? OK,不是的,我們在項目中一般用讀已提交(Read Commited)這個隔離級別! 居然是讀已提交。

我們先來思考一個問題,在Oracle、SqlServer中都是選擇讀已提交(Read Commited)作爲默認的隔離級別,爲什麼Mysql不選擇讀已提交(Read Commited)作爲默認隔離級別,而選擇可重複讀(Repeatable Read)作爲默認的隔離級別呢?

我們先明白一點!項目中是不用讀未提交(Read UnCommitted)和串行化(Serializable)兩個隔離級別,原因有二: 採用讀未提交(Read UnCommitted),一個事務讀到另一個事務未提交讀數據,這個不用多說吧,從邏輯上都說不過去!採用串行化(Serializable),每個次讀操作都會加鎖,快照讀失效,一般是使用Mysql自帶的分佈式事務功能時才使用該隔離級別!也就是說,我們該糾結都只有一個問題,究竟隔離級別是用讀已經提交呢還是可重複讀? 接下來對這兩種級別進行對比,講講我們爲什麼選讀已提交(Read Commited)作爲事務隔離級別!

MySQL間隙鎖

當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(GAP LOCK)。舉例來說,假如user表中只有101條記錄,其empid的值分別是 1,2,…,100,101,下面的SQL:

<span style="color:#000000"><code class="language-sql"><span style="color:#0077aa">select</span> <span style="color:#a67f59">*</span> <span style="color:#0077aa">from</span>  <span style="color:#0077aa">user</span> <span style="color:#0077aa">where</span> user_id <span style="color:#a67f59">></span> <span style="color:#986801">100</span> <span style="color:#0077aa">for</span> <span style="color:#0077aa">update</span><span style="color:#999999">;</span>
</code></span>
  • 1

這是一個範圍條件的檢索且要求加上排他鎖,InnoDB不僅會對符合條件的user_id值爲101的記錄加鎖,也會對user_id大於101(這些記錄並不存在)的“間隙”加鎖。

InnoDB使用間隙鎖的目的,一方面是爲了防止幻讀(爲了防止幻讀去鎖表則影響太大,會影響效率),以滿足相關隔離級別的要求,對於上面的例子,要是不使用間隙鎖,如果其他事務插入了user_id大於100的任何記錄,那麼本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是爲了滿足其恢復和複製的需要(發生幻讀時的binlog,如果直接拿到備庫去執行會發生了主備數據不一致的嚴重問題)

很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件;當然,對一條不存在的記錄加鎖,也會有間隙鎖的問題。

間隙鎖在InnoDB的唯一作用就是防止其它事務的插入操作,以此來達到防止幻讀的發生,所以間隙鎖不分什麼共享鎖與排它鎖。如果InnoDB掃描的是一個主鍵、或是一個唯一索引的話,那InnoDB只會採用行鎖方式來加鎖,而不會使用Next-Key Lock的方式,也就是說不會對索引之間的間隙加鎖。

要禁止間隙鎖的話,可以把隔離級別降爲讀已提交,或者開啓參數innodb_locks_unsafe_for_binlog。

RC與RR在鎖方面的區別

1、RR要用到間隙鎖,而RC則沒有間隙鎖。因爲MySQL的RR需要間隙鎖來解決幻讀問題。而RC隔離級別則是允許存在不可重複讀和幻讀的。所以RC的併發一般要好於RR;在RR隔離級別下,存在間隙鎖,導致出現死鎖的機率比RC大的多;
2、 RC 隔離級別,通過 where 條件過濾之後,不符合條件的記錄上的行鎖,會被釋放掉,但是RR隔離級別,即使不符合where條件的記錄,也不會釋放行鎖和間隙鎖,所以從鎖方面來看,RC的併發應該要好於RR;
3、
RC隔離級別時,事務中的每一條select語句會讀取到他自己執行時已經提交了的記錄,也就是每一條select都有自己的一致性讀ReadView; 而RR隔離級別時,事務中的一致性讀的ReadView是以第一條select語句的運行時,作爲本事務的一致性讀snapshot的建立時間點的,只能讀取該時間點之前已經提交的數據

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