概要:html
事務的四個特性:原子性、一致性、隔離性、持久性數據庫
事務不隔離帶來的問題:更新丟失、髒讀、不可重複讀、虛讀(幻讀)。其中更新丟失就是併發寫,這是必定不容許的,所以必定要解決更新丟失問題。session
事務隔離的級別:讀未提交(1000)、讀已提交(1100)、可重複讀(1110)、串行化(1111)。多線程
|
更新丟失併發 |
髒讀spa |
不可重複讀線程 |
幻讀3d |
RU(讀未提交)code |
避免htm |
|
|
|
RC(讀提交) |
避免 |
避免 |
|
|
RR(可重複讀) |
避免 |
避免 |
避免 |
|
S(串行化) |
避免 |
避免 |
避免 |
避免
|
若是一個數據庫聲稱支持事務的操做,那麼該數據庫必需要具有如下四個特性:
原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,這和前面兩篇博客介紹事務的功能是同樣的概念,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。
一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態,也就是說一個事務執行以前和執行以後都必須處於一致性狀態。
拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。
即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。
關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。
持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。
例如咱們在使用JDBC操做數據庫時,在提交事務方法後,提示用戶事務操做完成,當咱們程序執行完成直到看到提示後,就能夠認定事務以及正確提交,即便這時候數據庫出現了問題,也必需要將咱們的事務徹底執行完成,不然就會形成咱們看到提示事務處理完畢,可是數據庫由於故障而沒有執行事務的重大錯誤。
以上介紹完事務的四大特性(簡稱ACID),如今重點來講明下事務的隔離性,當多個線程都開啓事務操做數據庫中的數據時,數據庫系統要能進行隔離操做,以保證各個線程獲取數據的準確性。
在介紹數據庫提供的各類隔離級別以前,咱們先看看若是不考慮事務的隔離性,會發生的幾種問題:
更新丟失:兩事務同時更新,一個失敗回滾覆蓋另外一個事務的更新。或事務1執行更細操做,在事務1結束前事務2也更新,則事務1的更細結果被事務2的覆蓋了。
髒讀:事務T2讀取到事務T1修改了可是還未提交的數據,以後事務T1又回滾其更新操做,致使事務T2讀到的是髒數據。
不可重複讀:事務T1讀取某個數據後,事務T2對其作了修改,當事務T1再次讀該數據時獲得與前一次不一樣的值。
虛讀(幻讀):事務T1讀取在讀取某範圍數據時,事務T2又插入一條數據,當事務T1再次數據這個範圍數據時發現不同了,出現了一些「幻影行」。
髒讀和不可重複讀的區別:髒讀是某一事務讀取了另外一個事務未提交的髒數據,而不可重複讀則是讀取了前一事務提交的數據。
不可重複讀和幻讀的異同:都是讀取了另外一條已經提交的事務(這點就髒讀不一樣),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據總體(好比數據的個數)。
一、更新丟失(Lostupdate)
兩個事務同時更新,第二個事務回滾會覆蓋第一個事務更新的數據,致使更新丟失
二、兩次更新問題(Secondlost updates problem)
兩個事務都讀取了數據,並同時更新,第一個事務更新失敗,由於被第二個事務覆蓋。
髒讀是指在一個事務處理過程裏讀取了另外一個未提交的事務中的數據。
當一個事務正在屢次修改某個數據,而在這個事務中這屢次的修改都還未提交,這時一個併發的事務來訪問該數據,就會形成兩個事務獲得的數據不一致。例如:用戶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之後再次查看帳戶時就會發現錢其實並無轉。
不可重複讀是指在對於數據庫中的某個數據,一個事務範圍內屢次查詢卻返回了不一樣的數據值,這是因爲在查詢間隔,被另外一個事務修改並提交了。
例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據而且提交事務給數據庫,事務T1再次讀取該數據就獲得了不一樣的結果,發送了不可重複讀。
不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另外一個事務未提交的髒數據,而不可重複讀則是讀取了前一事務提交的數據。
在某些狀況下,不可重複讀並非問題,好比咱們屢次查詢某個數據固然以最後查詢獲得的結果爲主。但在另外一些狀況下就有可能發生問題,例如對於同一個數據A和B依次查詢就可能不一樣,A和B就可能打起來了……
幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中全部的行的某個數據項作了從「1」修改成「2」的操做,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值仍是爲「1」而且提交給數據庫。而操做事務T1的用戶若是再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺同樣,這就是發生了幻讀。
幻讀和不可重複讀的異同:都是讀取了另外一條已經提交的事務(這點就髒讀不一樣),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據總體(好比數據的個數)。
爲此咱們須要經過提供不一樣類型的「鎖」機制針對數據庫事務進行不一樣程度的併發訪問控制,由此產生了不一樣的事務隔離級別:隔離級別(低->高)。SQL、SQL2標準定義了四種隔離級別:
●讀未提交(Read Uncommitted)
含義解釋:只限制同一數據寫事務禁止其餘寫事務。解決」更新丟失」。(一事務寫時禁止其餘事務寫)
名稱解釋:可讀取未提交數據
所需的鎖:排他寫鎖
●讀提交(Read Committed)
含義解釋:只限制同一數據寫事務禁止其它讀寫事務。解決」髒讀」,以及」更新丟失」。(一事務寫時禁止其餘事務讀寫)
名稱解釋:必須提交之後的數據才能被讀取
所需的鎖:排他寫鎖、瞬間共享讀鎖
●可重複讀(Repeatable Read)
含義解釋:限制同一數據寫事務禁止其餘讀寫事務,讀事務禁止其它寫事務(容許讀)。解決」不可重複讀」,以及」更新丟失」和」髒讀」。(一事務寫時禁止其餘事務讀寫、一事務讀時禁止其餘事務寫)
注意沒有解決幻讀,解決幻讀的方法是增長範圍鎖(range lock)或者表鎖。
名稱解釋:可以重複讀取
所需的鎖:排他寫鎖、共享讀鎖
●串行化(Serializable)
含義解釋:限制全部讀寫事務都必須串行化實行。它要求事務序列化執行,事務只能一個接着一個地執行,但不能併發執行。若是僅僅經過「行級鎖」是沒法實現事務序列化的,必須經過其餘機制保證新插入的數據不會被剛執行查詢操做的事務訪問到。(一事務寫時禁止其餘事務讀寫、一事務讀時禁止其餘事務讀寫)
所須的鎖:範圍鎖或表鎖
下表是各隔離級別對各類異常的控制能力。
|
更新丟失 |
髒讀 |
不可重複讀 |
幻讀 |
RU(讀未提交) |
避免 |
|
|
|
RC(讀提交) |
避免 |
避免 |
|
|
RR(可重複讀) |
避免 |
避免 |
避免 |
|
S(串行化) |
避免 |
避免 |
避免 |
避免
|
以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,固然級別越高,數據完整性越好,但執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(相似於Java多線程中的鎖)使得其餘的線程只能在鎖外等待,因此平時選用何種隔離級別應該根據實際狀況。
數據庫 |
默認級別 |
MySQL |
可重複讀(Repeatable Read) |
Oracle |
讀提交(Read Committed) |
SQLServer |
讀提交(Read Committed) |
DB2 |
讀提交(Read Committed) |
PostgreSQL |
讀提交(Read Committed)
|
在MySQL數據庫中,支持上面四種隔離級別,默認的爲Repeatable read (可重複讀);此外,MySQL的Repeatable Read隔離級別也解決了幻讀問題(經過Next-key lock加鎖方法即範圍鎖解決不可重複讀和幻讀問題,如select * from t where a>10會對key爲[10,infinite)範圍的行加鎖,這樣其餘事務就不能對此範圍內key對應的行更改)達到了SQL、SQL2標準中的Serializable級別。
在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的爲Read committed級別。
在MySQL數據庫中查看當前事務的隔離級別:
select @@tx_isolation;
在MySQL數據庫中設置事務的隔離 級別:
set [glogal | session] transaction isolation level 隔離級別名稱; //設置所有鏈接或當前鏈接的事務隔離級別 set tx_isolation=’隔離級別名稱; //設置當前鏈接的事務隔離級別
例1:查看當前事務的隔離級別:
例2:將事務的隔離級別設置爲Read uncommitted級別:
或:
記住:設置數據庫的隔離級別必定要是在開啓事務以前!
若是是使用JDBC對數據庫的事務設置隔離級別的話,也應該是在調用Connection對象的setAutoCommit(false)方法以前。調用Connection對象的setTransactionIsolation(level)便可設置當前連接的隔離級別,至於參數level,可使用Connection對象的字段:
在JDBC中設置隔離級別的部分代碼:
後記:隔離級別的設置只對當前連接有效。對於使用MySQL命令窗口而言,一個窗口就至關於一個連接,當前窗口設置的隔離級別只對當前窗口中的事務有效;對於JDBC操做數據庫來講,一個Connection對象至關於一個連接,而對於Connection對象設置的隔離級別只對該Connection對象有效,與其餘連接Connection對象無關。
參考資料: