一、什么是事務?
事務是應用程序中一系列嚴密的操作,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤消。也就是事務具有原子性,一個事務中的一系列的操作要么全部成功,要么一個都不做。
事務的結果有兩種:當事務中的所有步驟全部成功執行完成時,事務提交。如果其中一個步驟失敗,將發生回滾操作,撤消之前到事務開始時的所有操作。
二、事務的四個特性
事務具有四個特征:原子性( Atomicity )、一致性( Consistency )、隔離性( Isolation )和持續性( Durability )。這四個特性簡稱為 ACID 特性。
原子性:事務是數據庫的邏輯工作單位,事務中包含的各操作要么都做,要么都不做。
一致性:事務的執行結果必須是使數據庫從一個一致性狀態變到另一個一致性狀態。因此當數據庫只包含成功事務提交的結果時,就說數據庫處於一致性狀態。如果數據庫系統運行中發生故障,有些事務尚未完成就被迫中斷,這些未完成事務對數據庫所做的修改有一部分已經寫入了物理數據庫,這時數據庫就處於一種不正確的狀態,或者說是不一致的狀態。
隔離性:一個事務的執行不能受其它事務的干擾。即並發執行的各個事務之間不能互相干擾。
持續性:也稱永久性,指一個事務一旦提交,它對數據庫中的數據的改變就應該是永久性的。接下來的其它操作或故障不應該對其執行結果有任何影響。
三.三大並發問題
臟數據:它所指的就是未提交的數據。也就是說,一個事務正在對一條記錄做修改,在這個事務完成並提交之前,這條數據是處於待定狀態的(,這時,第二個事務來讀取這條沒有提交的數據,並據此做進一步的處理,就會產生對未提交的數據的依賴關系。這種現象被稱為臟讀。
不可重復讀(Non-Repeatable Reads):一個事務先后讀取同一條記錄,事務在兩次讀取之間該數據被其它事務所修改,則兩次讀取的數據不同,稱之為不可重復讀。
幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為幻讀。
四.四種鎖
從數據庫系統的角度來看,鎖分為共享鎖和排他鎖,從程序員的角度看,鎖分為悲觀鎖和樂觀鎖。
(1)共享鎖(S鎖, 讀鎖)
共享鎖鎖定的資源可以被其它用戶讀取,但其它用戶不能修改它。在SELECT 命令執行時,SQL Server 通常會對對象進行共享鎖鎖定。通常加共享鎖的數據頁被讀取完畢后,共享鎖就會立即被釋放。
(2)排他鎖(X鎖,寫鎖)
排他鎖鎖定的資源只允許進行鎖定操作的程序使用,其它任何對該資源的操作均不會被接受。執行數據更新命令,即INSERT、UPDATE 或DELETE 命令時,SQL Server 會自動使用排他鎖。但當對象上有其它鎖存在時,無法對其加排他鎖。排他鎖一直到事務結束才能被釋放。共享鎖可以一層套一層的上鎖,但排他鎖只能加一個。
(3)樂觀鎖
總是認為不會產生並發問題,每次去取數據的時候總認為不會有其他線程對數據進行修改,因此不會上鎖,但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用版本號機制或CAS操作實現。
version方式:一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
(4)悲觀鎖
總是假設最壞的情況,每次取數據時都認為其他線程會修改該數據,所以都會加鎖(讀鎖、寫鎖、行鎖等),當其他線程想要訪問數據時,都要阻塞掛起。可以依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操作之前加鎖,在Java中,synchronized的思想也是悲觀鎖。根據范圍,鎖還可以划分成行級鎖和表鎖。
五、MySQL的四種隔離級別
(1)Read uncommitted:讀未提交,顧名思義,就是一個事務可以讀取另一個未提交事務的數據。
事例:老板要給程序員發工資,程序員的工資是3.6萬/月。但是發工資時老板不小心按錯了數字,按成3.9萬/月,該錢已經打到了程序員的戶口,但是事務還沒有提交,就在這時,剛好程序員去查看自己這個月的工資,發現比往常多了3千元,以為漲工資了非常高興。但是老板及時發現了不對,馬上回滾差點就提交了的事務,將數字改成3.6萬再提交。
分析:實際程序員這個月的工資還是3.6萬,但是程序員看到的是3.9萬。他看到的是老板還沒提交事務時的數據。這就是臟讀。
那怎么解決臟讀呢?Read committed!讀提交,能解決臟讀問題。
(2)Read committed:讀提交,顧名思義,就是一個事務要等另一個事務提交后才能讀取數據。
事例:程序員拿着信用卡去享受生活(卡里當然是只有3.6萬),當他買單時(程序員事務開啟),收費系統事先檢測到他的卡里有3.6萬,就在這個時候!!程序員的妻子要把錢全部轉出充當家用,並提交該事物。當收費系統准備扣款時,再檢測卡里的金額,發現已經沒錢了(第二次檢測卡里的金額當然要等待妻子轉出金額事務提交完,才能檢測)。程序員就會很郁悶,明明卡里是有錢的…
分析:這就是讀提交,若有事務對數據進行更新(UPDATE)操作時,讀操作事務要等待這個更新操作事務提交后才能讀取數據,可以解決臟讀問題。
但在這個事例中,出現了一個事務范圍內兩個相同的查詢卻返回了不同數據,這就是不可重復讀。
那怎么解決不可重復讀問題?Repeatable read !,重復讀。
(3)Repeatable read:重復讀,就是在開始讀取數據(事務開啟)時,不再允許修改操作發生。
事例:程序員拿着信用卡去享受生活(卡里當然是只有3.6萬),當他買單時(事務開啟,不允許其他事務的UPDATE修改操作),收費系統事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉出金額了。接下來收費系統就可以扣款了。
分析:重復讀可以解決不可重復讀問題。寫到這里,應該明白的一點就是,不可重復讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因為幻讀問題對應的是插入INSERT操作,而不是UPDATE操作。
什么時候會出現幻讀?
事例:程序員某一天去消費,花了2千元,然后他的妻子去查看他今天的消費記錄(妻子事務開啟),看到確實是花了2千元,就在這個時候(時間間隙),程序員又花了1萬買了一部電腦,即新增INSERT了一條消費記錄,並提交。當妻子打印程序員的消費記錄清單時(妻子事務提交),發現花了1.2萬元,似乎出現了幻覺,這就是幻讀。
那怎么解決幻讀問題?Serializable!
(4)Serializable 序列化:Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執行,可以避免臟讀、不可重復讀與幻讀。但是這種事務隔離級別效率低下,比較耗數據庫性能,一般不使用。
事務的隔離級別 |
臟讀 |
不可重復讀 |
幻讀 |
Read uncommitted(讀未提交) |
√ |
√ |
√ |
Read committed(讀提交) |
× |
√ |
√ |
Repeatable read(重復讀) |
× |
× |
√ |
Serializable(序列化) |
× |
× |
× |
六.總結
大多數數據庫默認的事務隔離級別是Read committed(讀提交),比如Sql Server , Oracle。
Mysql的默認隔離級別是Repeatable read(重復讀)。
隔離級別越高,越能保證數據的完整性和一致性,但是對並發性能的影響也越大。對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed(讀提交),它能夠避免臟讀,而且具有較好的並發性能。盡管它會導致不可重復讀、幻讀和第二類丟失更新這些並發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。
注意:事務的隔離級別和數據庫的並發性是成反比的,隔離級別越高,並發性越低。