MySQL 事务 :ACID、并发带来的问题、事务的隔离级别、事务的实现
文章目錄
- ACID
- 原子性
- 一致性
- 持久性
- 隔離性
- 并發帶來的隔離問題
- 幻讀(虛讀)
- 不可重復讀
- 臟讀
- 丟失更新
- 隔離級別
- Read Uncommitted (讀未提交)
- Read Committed (讀已提交)
- Repeatable Read (可重復讀)
- Serializable (可串行化)
- 事務的使用
- 事務的實現
- redo
- undo
事務指邏輯上的一組操作,組成這組操作的各個單元,要么全部成功,要么全部失敗。
在MySQL中MyISAM引擎并不支持事務,所以這里指的主要是InnoDB引擎
ACID
談到事務,那肯定少不了ACID的特性,ACID是以下幾個單詞的縮寫,下面一一對其進行介紹
- 原子性(atomicity)
- 一致性(consistency)
- 隔離性(isolation)
- 持久性(durability)
原子性
原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
例如我們去ATM機取錢,流程如下
上述的過程就應該視為一個原子操作,要么全都發生,要么就不發生。因為不可能扣賬戶中的錢,ATM卻不出鈔票,又或者是出了鈔票,我的賬戶卻沒發生變化,這種行為是不容許發生的。
一致性
一致性即事務操作前與操作后的狀態始終一致。
如何理解呢?就好比我們此時有用戶A和用戶B,他們的余額分別為300元和700元,此時兩人總金額為1000元。此時若是用戶B向用戶A轉賬200元,則兩者的此時都有500元,總金額還是1000元。
也就是說,無論我們兩個怎么轉賬,總金額它只會是1000,既不會多,也不會少。這就是事務操作前后的狀態始終一致。倘若錢多了或者少了,都代表著事務將數據庫從一種狀態變為了另外一種狀態,此時就不再符合一致性了。
持久性
持久性指的是事務一旦提交,其結果就是永久性的。即使發生了服務器宕機的事故,數據庫也能成功的將數據給恢復。
但是需要注意的是,只能從事務本身的角度來保證結果的永久性,即事務提交后所有變化都是永久的,但是這僅僅局限于數據庫本身發生的問題,倘若是由于外部原因如RAID卡損壞、天災人禍導致數據庫發生問題,那么即使事務提交了,也可能會丟失。
基于上述原因,持久性只能保證事務系統的高可靠性,而無法保證其高可用性。
隔離性
隔離性指的是每個讀寫事務的對象之間相互隔離,即該事務提交前對其他事務都不可見。
由于這一特性,在事務并發時也會帶來以下問題。
并發帶來的隔離問題
幻讀(虛讀)
幻讀指在同一事務中,用同樣的操作讀取兩次,得到的記錄數卻不一樣。(針對同一個范圍的數據)。 主要原因就是當第一個事務對表中的數據進行修改,并且這個涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,向其中插入了一行。這樣也就導致了操作第一個事務的用戶發現表中還有沒修改的數據行,像發生了幻覺一樣。
明明在會話A的第一次查詢中,大于2的數只有行只有一列,而由于會話B插入了新列后,對于會話A而言就憑空多出來了一列,像出現了幻覺一樣。
不可重復讀
不可重復讀指的是在一個事務中多次讀取同一行數據,但是多次讀取的數據卻不一樣(主要針對同一行數據)。導致這一問題的主要原因就是一個事務讀取到了其他事務已提交的數據。
例如以下情況
由于其他事務的干擾,對于事務A來說,兩次讀取的金額都不一樣。
因為不可重復讀讀到的是已經提交的數據,由于其本身并不會帶來很大的問題,所以大部分數據庫廠商都會允許這種情況的發生,將隔離級別設置為 READ COMMITTED(讀已提交)
臟讀
臟讀即一個事務讀取到了另外一個事務中未提交的數據,也就是可能因為其他事務對數據進行修改或者回滾導致的問題。
例如下圖,會話B在第一次查看時表中只有一條數據,但是在第五階段中會話A向表中插入了另一條數據,這就導致了會話B在讀取的時候得到的結果就不再一樣,因為它讀取到了臟數據。
臟讀的現象并不會經常發生,因為臟讀發生的條件是需要事務的隔離級別為READ UNCOMMITTED(讀未提交),而大部分數據庫的默認隔離級別都為READ COMMITTED。
丟失更新
丟失更新就是一個事務的更新操作會被另外一個事務的更新操作所覆蓋,從而導致數據的不一致。例如以下案例
此時由于B將A的修改覆蓋,導致A雖然提交,但是更新卻丟失了,只剩下了B的更新。
但是在當前數據庫的任何隔離級別下,都不會導致理論意義上的丟失更新問題,即使是隔離級別最低的Read Uncommitted,也由于加鎖保護,所以事務B的修改操作會被阻塞,直到事務A提交。
這也就是為什么通常大家討論的只有幻讀、臟讀、丟失更新的原因。
隔離級別
為了解決上述問題,MySQL中實現了以下四種隔離級別,隔離級別由下往上以此增加
需要注意的是隔離級別越高,事務請求的鎖也就越多,保持鎖的時間也就越長。所以隔離性越強,并發的效率也就越低。
Read Uncommitted (讀未提交)
在該隔離級別下,所有事務都可以看到其他未提交事務的執行結果。在該級別下,雖然并發的效率最高,但是安全性完全沒有得到保護,所以很少用于實際應用。
Read Committed (讀已提交)
該隔離級別使大部分數據庫默認的隔離級別,如Oracle、SQL Server等。該隔離級別滿足了隔離的簡單定義,即一個事務只能看見提交了的事務所做的改變,雖然它還有不可重復讀的問題,但是我前面也說了,不可重復讀本身并不是一個大問題,所以為了兼顧到性能,大部分數據庫都會容許這種問題的產生。
Repeatable Read (可重復讀)
這是MySQL中InnoDB默認的隔離級別,它確保同一事務的多個實例在并發讀取數據時,會看到同樣的數據行。也正是因為這樣,又導致了幻讀的問題,但是InnoDB可以借助MVCC中的Next-Key Locking的加鎖方式來解決這個問題,具體的解決方法我會在下一篇博客中講解。
Serializable (可串行化)
這是最高的隔離級別,通過強制事務進行排序,使事務之間不可能互相沖突,從而解決了其他隔離級別無法解決的幻讀問題。由于其在每個讀的數據行上加了共享鎖,所以在該隔離級別下可能會導致大量的超時現象以及鎖競爭。
這四種隔離級別分別可能發生的問題如下圖所示
事務的使用
開啟事務
START TRANSACTION 或者 BEGIN (由于MySQL的數據分析器會自動將BEGIN識別為BEGIN...END,所以在存儲過程中只能使用START TRANSACTION來開啟事務)提交事務
COMMIT回滾事務
//回滾整個事務 ROLLBACK//回滾至某個保存點 ROLLBACK TO SAVEPOINT 保存點ID設置保存點
SAVEPOINT 保存點ID刪除保存點
RELEASE SAVEPOINT 保存點ID設置隔離級別
SET TRANSACTION事務的實現
事務的持久性主要依靠redo log(重做日志)來完成,而原子性、一致性則通過undo log(撤銷日志)來完成。至于隔離性,則通過鎖來完成,由于這一章主要講解事務,所以鎖的部分我會放到下一篇博客中,下面就分別介紹一下redo log 和 undo log分別是什么,如何實現事務的特性。
首先要知道,redo和undo的左右都可以視為一種恢復操作,所以undo絕不是redo的逆過程。其中redo恢復提交事務修改的頁操作,而undo回歸行記錄到某個特定版本。也因為如此,兩者記錄的內容不同,redo通常是物理日志,記錄的是頁的物理修改操作。而undo是邏輯日志,根據每行記錄進行記錄。
redo
redo log由兩部分組成,一是存在于內存中的重做日志緩沖(redo log buff),由于存在內存中,所以其具有易失性。二是重做日志文件(redo log file),其存在于硬盤中,所以是持久的。
redo主要通過Force Log at Commit機制來實現事務的持久性。
步驟如下
當事務提交時,將該事務的所有重做日志從緩沖區中寫入到重做日志文件進行持久化,當事務的提交完成后才算完成。為了確保每次日志每次都寫入文件中,在每次將日志緩沖寫入日志文件后,都會發起一次異步操作(fsync)
為什么需要這個異步調用呢?因為重做日志文件打開時并沒有使用O_DIRECT選項,所以重做日志緩沖會先寫入文件系統緩沖,為了保證其能夠成功寫入磁盤,必須發起一次異步調用。由于異步調用的效率取決于磁盤的性能,因此磁盤的性能決定了事務提交的性能,即數據庫性能。
undo
當事務需要回滾操作的時候,就需要用到undo。undo是撤銷日志,其中保留了數據庫各個版本的狀態,我們可以借助undo邏輯地將數據庫恢復到原來地樣子。除了進行回滾之外,undo的另一個作用就是實現MVCC,這一部分我會在下一篇博客中重點講解。
首先看看undo log的生成流程
從一上兩圖可以看出,每當事務發生變更的時候,都會伴隨著undo log的產生,并且為了防止其丟失,undo log會比數據先持久化到硬盤上。
由于undo log是邏輯日志,所以其中記錄的都是對于數據庫的操作指令。而事務的回滾,其實也就是根據這個操作來進行一個逆向操作。如下面幾種
- 當執行一個insert指令時,其逆向指令為delete
- 當執行一個delete指令時,其逆向指令為insert
- 當執行一個update指令時,其逆向指令為update
原子性就是借助以上機制實現,倘若事務中的某一個步驟未能成功完成,則借助undo log中存儲的記錄來回滾到事務的最原始狀態
而至于一致性,則主要依靠上述的其他三種特性來實現,也就是說一致性是目的,而原子性、隔離性、持久性則是數據庫實現一致性的手段,只有滿足這三個性質,才能夠保證一致性。
總結
以上是生活随笔為你收集整理的MySQL 事务 :ACID、并发带来的问题、事务的隔离级别、事务的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL 索引 :哈希索引、B+树索引
- 下一篇: linux cmake编译源码,linu