1. 程式人生 > >資料庫的隔離級別及相關...

資料庫的隔離級別及相關...

資料庫的隔離級別

要明白隔離級別,得先明白資料庫中的事務(Database Transaction)

資料庫事務(Database Transaction)是指作為單個邏輯工作單元執行的一系列操作,要麼完整地執行,要麼完全地不執行。 正常的情況下,這些操作將順利進行,最終交易成功,與交易相關的所有資料庫資訊也成功地更新。但是,如果在這一系列過程中任何一個環節出了差錯,資料庫中所有資訊都必須保持交易前的狀態不變,比如最後一步更新使用者資訊時失敗而導致交易失敗,那麼必須保證這筆失敗的交易不影響資料庫的狀態–庫存資訊沒有被更新、使用者也沒有付款,訂單也沒有生成。否則,資料庫的資訊將會一片混亂而不可預測。

簡單點:執行多條sql語句(DML),這些語句要麼都成功,要麼都失敗,保證資料庫不出現因為DML操作導致的資料問題。


四個特性:

1. 原子性(atomic)(atomicity)

原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則完全不能對資料庫有任何影響。
簡單點:針對某一個事務,要麼成功,要麼失敗。

2. 一致性(consistent)(consistency)

一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。
拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。
簡單點:針對某一個事務,事務前後的資料要都合法。

3. 隔離性(insulation)(isolation)

隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。
簡單點:針對多個並行的事務,各幹各的事,互不干擾。

4. 永續性(Duration)(durability)

事務完成之後,它對於系統的影響是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。
簡單點:針對某一個事務,成功結束後,結果永遠不變。



什麼是隔離級別

一個事務必須與由其他事務進行的資源或資料更改相隔離的程度。當多個執行緒都開啟事務操作資料庫中的資料時,資料庫系統要能進行隔離操作,以保證各個執行緒獲取資料的準確性。

簡單點:針對多個 並行 的事務,如果事務之間必須有資料的互動,則通過這個隔離級別來保證資料的準確性。

並行事務,單個或順序執行的事務不涉及隔離級別。


要明白隔離級別,還得明白如果沒有這個隔離級別的設定,會出現什麼問題

1、更新丟失

兩個事務都同時更新一行資料,一個事務對資料的更新把另一個事務對資料的更新覆蓋了。這是因為系統沒有執行任何的鎖操作,因此併發事務並沒有被隔離開來。
A事務更新的一個值,B事務也更新了這個值,兩個最後還都提交了。
簡單點:(更新被覆蓋了)A事務的更新被B事務更新覆蓋了。

2. 髒讀

指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。

當一個事務正在多次修改某個資料,而在這個事務中這多次的修改都還未提交,這時一個併發的事務來訪問該資料,就會造成兩個事務得到的資料不一致。例如:使用者A向用戶B轉賬100元,對應SQL命令如下

update account set money=money+100 where name=’B’;  (此時A通知B)

update account set money=money - 100 where name=’A’;

當只執行第一條SQL時,A通知B檢視賬戶,B發現確實錢已到賬(此時即發生了髒讀),而之後無論第二條SQL是否執行,只要該事務不提交,則所有操作都將回滾,那麼當B以後再次檢視賬戶時就會發現錢其實並沒有轉。
簡單點:(讀了臨時資料)A事務修改了、但未提交的資料,被B事務讀取了,A最後還回滾了。。。

3. 不可重複讀

指在對於資料庫中的某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔,被另一個事務修改並提交了。

例如事務T1在讀取某一資料,而事務T2立馬修改了這個資料並且提交事務給資料庫,事務T1再次讀取該資料就得到了不同的結果,傳送了不可重複讀。

不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另一個事務未提交的髒資料,而不可重複讀則是讀取了前一事務提交的資料。

在某些情況下,不可重複讀並不是問題,比如我們多次查詢某個資料當然以最後查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對於同一個資料A和B依次查詢就可能不同,A和B就可能打起來了……
簡單點:(查詢不一致)A事務重複查詢某個值,結果這個值不一樣,因為被B事務修改了。

4. 虛讀(幻讀)

幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個資料項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行資料項,而這個資料項的數值還是為“1”並且提交給資料庫。而操作事務T1的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務T2中新增的,就好像產生幻覺一樣,這就是發生了幻讀。

幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。
簡單點:(更新時落資料)A事務更新了些資料,提交後發現還露了一個,原來這個是B事務剛加進去的。



好,現在開始隔離級別(四種)

1. Read uncommitted(未授權讀取、讀未提交)

如果一個事務已經開始寫資料,則另外一個事務則不允許同時進行寫操作,但允許其他事務讀此行資料。該隔離級別可以通過“排他寫鎖”實現。

避免了更新丟失,卻可能出現髒讀。也就是說事務B讀取到了事務A未提交的資料。

簡單點:未提交的資料只可以被讀取。

2. Read committed(授權讀取、讀提交)

讀取資料的事務允許其他事務繼續訪問該行資料,但是未提交的寫事務將會禁止其他事務訪問該行。

該隔離級別避免了髒讀,但是卻可能出現不可重複讀。事務A事先讀取了資料,事務B緊接了更新了資料,並提交了事務,而事務A再次讀取該資料時,資料已經發生了改變。

簡單點:只能讀提交後的資料。

3. Repeatable read(可重複讀取)

讀取資料的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。

避免了不可重複讀取和髒讀,但是有時可能出現幻讀。這可以通過“共享讀鎖”和“排他寫鎖”實現

簡單點:讀就是讀(多讀),寫就是寫(單寫),不能一起幹。

4. Serializable(序列化)

提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接著一個地執行,但不能併發執行。如果僅僅通過“行級鎖”是無法實現事務序列化的,必須通過其他機制保證新插入的資料不會被剛執行查詢操作的事務訪問到。

序列化是最高的事務隔離級別,同時代價也花費最高,效能很低,一般很少使用,在該級別下,事務順序執行,不僅可以避免髒讀、不可重複讀,還避免了幻像讀。

簡單點:一個一個順序執行。

  • 隔離級別最低的是Read uncommitted級別
  • 隔離級別最高的是Serializable級別,
  • 級別越高,執行效率就越低。
  • 大多數資料庫的預設級別就是Read committed。
  • 在MySQL資料庫中預設的隔離級別為Repeatable read (可重複讀)。

像Serializable這樣的級別,就是以鎖表的方式(類似於Java多執行緒中的鎖)使得其他的執行緒只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL資料庫中預設的隔離級別為Repeatable read (可重複讀)。

在這裡插入圖片描述

附加一個事務的傳播屬性:

事務傳播屬性是spring針對業務上多個事務處理方案封裝的方案,不是資料庫中的。

  1. PROPAGATION_REQUIRED – 支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。

當前方法必須要求開啟事務,如果當前執行緒不存在事務,則開啟新的事務,如果當前執行緒已經存在事務,就加入到當前事務。這個是經常使用的。但是要注意的就是一旦事務中某一個方法回滾,當前事務上下文裡面所有的操作都回滾。

  1. PROPAGATION_REQUIRES_NEW – 新建事務,如果當前存在事務,把當前事務掛起。

當前方法必須要求開啟新的事務,如果當前執行緒已經存在事務上下文,就暫停當前事務,等到新事務結束之後,再繼續恢復之前的事務。兩個事務之間不會互相影響。經常可以用到的場景就是在業務發生異常的時候傳送短訊息。如果業務發生異常,業務回滾,但是由於傳送段訊息是新的事務,不會受到業務異常的影響。

  1. PROPAGATION_MANDATORY – 支援當前事務,如果當前沒有事務,就丟擲異常。

當前方法必須要求事務,如果當前執行緒不存在事務,就丟擲異常,如果存在,就加入到事務裡。

  1. PROPAGATION_SUPPORTS – 支援當前事務,如果當前沒有事務,就以非事務方式執行。

當前方法支援事務,如果當前執行緒存在事務,就加入到事務中去,如果不存在,不做任何操作。

  1. PROPAGATION_NOT_SUPPORTED – 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

當前方法不支援事務,如果當前執行緒存在事務,就掛起當前事務,執行完當前方法,恢復事務。一般情況下在查詢的時候使用,如果一個方法只是查詢,並且非常耗時,就可以使用Not Support,避免事務時間超長。

  1. PROPAGATION_NEVER – 以非事務方式執行,如果當前存在事務,則丟擲異常。

當前方法不支援事務,如果當前執行緒存在事務,則丟擲異常。這種用的比較少。

  1. PROPAGATION_NESTED – 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,


實戰

  • 在MySQL資料庫中 檢視 當前事務的隔離級別:
    select @@tx_isolation;
  • 在MySQL資料庫中設定事務的隔離 級別:
    set  [glogal | session]  transaction isolation level 隔離級別名稱;
--或者
    set tx_isolation=’隔離級別名稱;

JDBC的設定:

	Connection connection = getConnection();
	try {
		//設定隔離級別
		connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		//開啟事務
		connection.setAutoCommit(false);

Spring中的事務配置:

  1. 注入事務管理的元件Bean
<!-- 配置事務管理元件 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dbcp">  <!-- dbcp是連線池元件(org.apache.commons.dbcp2.BasicDataSource)的bean -->
</bean>
  1. 使用方式
  • 註解:
<!-- 採用註解方式:有原始碼的情況下,將註解加在方法上 -->
<!-- 開啟事務註解標記@Transactional,當呼叫帶@Transactional標記的方法時,將txManager的事務管理功能切入進去 -->
<tx:annotation-driven transactional-manager="txManager" />
<!-- 在需要事務管理的方法上加上@Transactional註解即可 -->
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class, RuntimeException.class})
  • 注入DataSourceTransactionManager
@Autowired  
private DataSourceTransactionManager txManager; 

public void updateDb(Bean bean) {  
	DefaultTransactionDefinition def = new DefaultTransactionDefinition();
	def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
	TransactionStatus status = txManager.getTransaction(def); 
    try { 
        //do someThing
        txManager.commit(status);  
    } catch (Exception e) {  
        txManager.rollback(status);  
    }  
}  

注意:

  • 設定資料庫的隔離級別一定要是在開啟事務之前。
  • 隔離級別的設定只對當前連結有效。
  • @Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
  • 預設情況下,一個有事務方法, 遇到RuntiomeException 時會回滾 . 遇到 受檢查的異常 是不會回滾 的. 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) 。

在這裡插入圖片描述