該系列文章只是爲了掃盲大家對於事務的理解,並不會太深入涉及具體底層實現,但是會介紹部分原理
四大特性
- 原子性(Atomicity)
一個事務必須被視爲一個不可風格的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾。
- 一致性(Consistency)
數據庫總是從一個一致性的狀態轉到另一個一致性的狀態。
- 隔離性(Isolation)
通常來說,一個事務所做的修改在未提交之前,對其他事務是不可見的。
- 持久性(Durability)
一旦事務提交,則其所做的修改就會永久保存到數據庫中,即使系統崩潰,修改的數據也不會丟失
隔離級別
- 未提交讀(Read Uncommitted)
在該級別,事務中的修改,即使未被提交,對其他事務也是可見的。事務可以讀取未提交的數據,這也被稱爲
髒讀(Dirty Read)
。
- 提交讀(Read Committed)
一個事務開始時,只能看見已經提交的事務所做的修改。換句話說,一個事務從開始到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時也叫做
不可重複讀(nonrepeatable read)
,因爲兩次執行同樣的查詢,可能會得到不一樣的結果。
- 可重複讀(Repeatable Read)
解決了
髒讀
,該級別保證了在同一個事務中多次讀取同樣記錄的結果是一致的。但是理論上,該級別無法解決幻讀(Phanto Read)
問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB
通過多版本併發控制(MVCC)
解決了幻讀問題。
可重複讀時MySQL的默認事務隔離級別。
- 可串行化(Serializable)
強制事務串行執行,避免了
幻讀
問題
如何保證事務?
Undo Log 實現事務
回滾日誌,提供
回滾
操作,記錄某數據被修改前的值,可以用來在事務失敗時進行rollback,保證事務一致性;同時也用於多版本併發控制
例如某一事務的事務序號爲T1,其對數據X進行修改,設X的原值是5,修改後的值爲15,那麼Undo日誌爲<T1, X, 5>
執行過程
假設有A、B兩個數據,值分別爲1,2。
前提條件:數據都是先讀到內存中,然後修改內存中的數據,最後將數據寫回磁盤
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄B=2到undo log.
E.修改B=4.
F.將undo log寫到磁盤。
G.將數據寫到磁盤。
H.事務提交
特點:
A. 更新數據前記錄Undo log。
B. 爲了保證持久性,必須將數據在事務提交前寫到磁盤。只要事務成功提交,數據必然已經持久化。
C. Undo log必須先於數據持久化到磁盤。如果在G,H之間系統崩潰,undo log是完整的,可以用來回滾事務。
D. 如果在A-F之間系統崩潰,因爲數據沒有持久化到磁盤。所以磁盤上的數據還是保持在事務開始前的狀態。
缺點:
- 每個事務提交前將數據和Undo Log寫入磁盤,這樣會導致大量的磁盤IO,因此性能很低。
- 如果能夠將數據緩存一段時間,就能減少IO提高性能。但是這樣就會喪失事務的持久性。
因此引入了另外一種機制來實現持久化,即Redo Log
Redo + Undo 實現事務
重做日誌,提供
前滾
操作。記錄某數據塊被修改後的值,防止在發生故障的時間點,尚有髒頁未寫入磁盤,在重啓mysql服務的時候,根據redo log進行重做,從而達到事務的持久性這一特性
例如某一事務的事務序號爲T1,其對數據X進行修改,設X的原值是5,修改後的值爲15,那麼Redo日誌爲<T1, X, 15>
執行過程
假設有A、B兩個數據,值分別爲1,2
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄A=3到redo log.
E.記錄B=2到undo log.
F.修改B=4.
G.記錄B=4到redo log.
H.將redo log寫入磁盤。
I.事務提交
特點
A. 爲了保證持久性,必須在事務提交前將Redo Log持久化。
B. 數據不需要在事務提交前寫入磁盤,而是緩存在內存中。
C. Redo Log 保證事務的持久性。
D. Undo Log 保證事務的原子性。
E. 有一個隱含的特點,數據必須要晚於redo log寫入持久存儲。
IO性能
Undo + Redo的設計主要考慮的是提升IO性能。雖說通過緩存數據,減少了寫數據的IO。但是卻引入了新的IO,即寫Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。
爲了保證Redo Log能夠有比較好的IO性能,InnoDB 的 Redo Log的設計有以下幾個特點:
A. 儘量保持Redo Log存儲在一段連續的空間上。因此在系統第一次啓動時就會將日誌文件的空間完全分配。以順序追加的方式記錄Redo Log,通過順序IO來改善性能。
B. 批量寫入日誌。日誌並不是直接寫入文件,而是先寫入redo log buffer.當需要將日誌刷新到磁盤時(如事務提交),將許多日誌一起寫入磁盤.
C. 併發的事務共享Redo Log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一起,以減少日誌佔用的空間。例如,Redo Log中的記錄內容可能是這樣的:
記錄1: <trx1, insert …>
記錄2: <trx2, update …>
記錄3: <trx1, delete …>
記錄4: <trx3, update …>
記錄5: <trx2, insert …>
D. 因爲C的原因,當一個事務將Redo Log寫入磁盤時,也會將其他未提交的事務的日誌寫入磁盤。
E. Redo Log上只進行順序追加的操作,當一個事務需要回滾時,它的Redo Log記錄也不會從
Redo Log中刪除掉。
checkpoint
定期將內存緩存區的內容刷新到數據庫磁盤文件。當遇到內存不足、緩存區已滿等情況時,需要將內容/部分內容(特別是髒數據)轉儲到數據庫磁盤文件中。在轉儲時,會記錄checkpoint發生的”時刻“。在故障恢復時,只需要redo/undo最近的一次checkpoint之後的操作
多版本併發控制(InnoDB)
MVCC是通過保存數據在某個時間點的快照來實現的,它的實現原理主要是依賴記錄中的 3個隱式字段(
DB_TRX_ID
、DB_ROLL_PTR
、DB_ROW_ID
),undo log
,read view
來實現的。
MVCC只在Repeatable Read
和Read Committed
兩個隔離級別下工作。其他兩個隔離級別都和MVCC不兼容,因爲Read Uncommitted
總是讀取最新的數據行,而不是符合當前事務版本的數據行。而Serializable
則會對所有讀取的行加鎖。
undo log
在操作任何數據之前,首先將數據備份到undo log中,然後進行數據的修改,所以undo log中存儲的是老版本數據,當一箇舊的事務需要讀取數據時,爲了能讀取到老版本的數據,需要順着undo鏈找到滿足其可見性的記錄。undo logs分爲:
insert undo log
和update undo log
insert undo log
:事務對insert新記錄時產生的undolog, 只在事務回滾時需要, 並且在事務提交後就可以立即丟棄。update undo log
:事務對記錄進行delete和update操作時產生的undo log, 不僅在事務回滾時需要, 一致性讀也需要,所以不能隨便刪除,只有當數據庫所使用的快照中不涉及該日誌記錄,對應的回滾日誌纔會被purge線程刪除。
隱式字段
DB_TRX_ID
(6字節)
(1)記錄最後一次修改(insert|update)本行記錄的事務id
(2)對於delete操作,在innodb看來也不過是一次update操作,更新行中的一個特殊位將行表示爲deleted, 並非真正刪除DB_ROLL_PTR
(7字節)
(1)指向寫入回滾段(rollback segment
)的undo log record
(2)如果一行記錄被更新, 則undo log record
包含 ‘重建該行記錄被更新之前內容’ 所必須的信息
read view
read view主要是用來做可見性判斷的,防止不該被事務看到的數據(例如還沒提交的事務修改的數據)被看到。 當創建read view時,會拷貝
trx_sys->descriptors
到read view中(read_view_t->descriptors
)。read_view_t->up_limit_id
是read_view_t->descriptors
這數組中最小的值,read_view_t->low_limit_id
是創建read view
時的max_trx_id
trx_sys
:維護了一個全局的活躍的讀寫事務id(trx_sys->descriptors
),id從小到大排序。表示在某個時間點,數據庫中所有的活躍(已經開始但還沒提交)的讀寫(必須是讀寫事務,只讀事務不包含在內)事務max_trx_id
:下一個事務ID。當創建一個事務時,會直接使用該值當作當前事務的ID,然後其自增repeatable read
:事務在begin/start transaction之後的第一條select讀操作後, 會創建一個read viewrepeatable committed
:事務中每條select語句都會創建一個read view
可見性算法
假設需要讀取的行的最後提交事務ID爲
trx_id_current
,新事務id爲new_id
,當前新開事務創建的read view
中最早的事務id爲up_limit_id
,最晚的事務id爲low_limit_id
trx_id_current < up_limit_id
:該行數據對新事務可見,轉5trx_id_current >= low_limit_id
:該行數據對新事務不可見,轉4up_limit_id <= trx_id_current < low_limit_id
:如果trx_id_current
在read_view_t->descriptors
中存在,則不可見,轉4;否則,可見,轉5- 從該行記錄的
DB_ROLL_PTR
指針所指向的回滾段中取出undo log record
,賦值給trx_id_current
,轉1 - 返回該行數據
案例分析
- MySQL8 設置會話隔離級別:
set session transaction_isolation='read-committed';
- MySQL8 查詢會話隔離級別:
SELECT @@transaction_isolation;
Repeatable Read
Read Committed
IO緩存
內核緩存
傳統的UNIX實現在內核中設有緩衝區高速緩存或頁面高速緩存。當數據寫入文件時,內核通常會把數據寫入其中一個緩衝區中,如果該緩衝區尚未寫滿,則並不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩衝區以便存放其他磁盤塊數據時,再將該緩衝排入輸出隊列,然後待其到達隊首時,才進行實際的I/O操作。這種輸出方式被稱爲
延遲寫(delayed write)
。
延遲寫減少了磁盤讀寫次數,但是卻降低了一致性,當系統故障時,可能會導致文件內容丟失。爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync
、fsync
和fdatasync
三個函數。
磁盤緩存
磁盤自身通常會有硬件緩存機制,對於寫操作,有
write back
和write through
兩種機制,前者將數據寫至緩存就會返回,而後者則會將數據寫到磁盤介質上。當使用write back機制時,fsync刷的文件數據可能只是寫到磁盤緩存就返回了,導致從應用看來,寫數據到磁盤的開銷很小(實際上並未執行磁盤寫操作);所以,使用write back機制時,即使上層應用顯式fsync成功,數據也是可能丟失的,比如緩存裏的數據還未刷到磁盤時掉電了,有些存儲設備會使用備用電池(BBU,Battery backup unit)
來避免掉電時緩存數據丟失。
如果要保證fsync調用成功後,數據一定持久化到磁盤,則要使用內核的write barrier
機制。該機制通過在IO操作之前和之後顯式刷新存儲設備的緩存來達到目的,在文件系統mount的時候可以指定是否開啓barrier機制,ext4
默認啓用barrier機制。
IO函數
sync
:將所有修改過的塊緩衝區排入寫隊列,然後就返回,它並不等待實際寫磁盤操作結束。通常稱爲update的系統守護進程會週期性地(一般每隔30秒)調用sync函數。這就保證了定期沖洗內核的塊緩衝區。fsync
:只對由文件描述符filedes指定的單一文件起作用,並且等待寫磁盤操作結束,然後返回。fsync可用於數據庫這樣的應用程序,這種應用程序需要確保將修改過的塊立即寫到磁盤上。fdatasync
:類似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。fflush
:標準I/O函數(如:fread,fwrite)會在內存建立緩衝,該函數刷新內存緩衝,將內容寫入內核緩衝,要想將其寫入磁盤,還需要調用fsync。msync
:對於內存映射(mmap)的文件數據,msync的功能與fsync類似,將內存映射數據刷到磁盤,msync使用時有3個標誌。
(1)MS_SYNC
:數據同步刷到磁盤後返回(可能只是寫到磁盤緩存,而msync調用後數據是否一定持久化,則要看存儲設備使用的緩存機制以及內核write barrier是否啓用。)
(2)MS_ASYNC
:對於更新的文件數據會提交IO操作到底層,但不會等IO操作執行完成,而是立即返回
(3)MS_INVALIDATE
:更新文件對應的其它映射數據(如內存區域M1、M2都映射了文件F的數據,如果在msync M1的時候指定了該標記,則M2內存區域裏的數據也會被更新)
Spring 事務使用
https://blog.csdn.net/ACMer_AK/article/details/78873683
https://blog.csdn.net/ACMer_AK/article/details/88390432
參考文獻
高性能MySQL(第3版)
https://segmentfault.com/a/1190000012650596
https://www.jianshu.com/p/8845ddca3b23
https://blog.csdn.net/weixin_34405925/article/details/85948013
https://blog.csdn.net/zhouxinlin2009/article/details/89633464
https://blog.csdn.net/lhb0709/article/details/86077584
https://blog.csdn.net/u012414189/article/details/84036550