前言
衆所周知,事務的一大特點是原子性,即同一事務的SQL要同時成功或者失敗。那大家有沒有想過在MySQL的innoDB存儲引擎中是如何保證這樣的原子性操作的?實際上它是利用事務執行過程中生成的日志undo log來實作的,那麼undo log究竟是怎麼一回事呢?
undo log介紹
大家不妨先思考下,如果事務中的SQL執行到一半,遇到報錯,需要把前面已經執行過的SQL撤銷以達到原子性的目的,這個過程也叫做"復原",該怎麼實作呢?
每當我們要對一條記錄做改動時(這裡的改動可以指INSERT、DELETE、UPDATE),把復原時所需的東西記下來。比如:
- 你插入一條記錄時,至少要把這條記錄的主鍵值記下來,之後復原的時候隻需要把這個主鍵值對應的記錄删
掉就好了
- 你删除了一條記錄,至少要把這條記錄中的内容都記下來,這樣之後復原時再把由這些内容組成的記錄插入
到表中就好了
- 你修改了一條記錄,至少要把修改這條記錄前的舊值都記錄下來,這樣之後復原時再把這條記錄更新為舊值
MySQL把這些為了復原而記錄的這些内容稱之為撤銷日志或者復原日志, 即undo log 。
undo log存儲形式
undo log的日志内容是邏輯日志,非實體日志。
- 實體日志的意思是指具體的把具體某個資料頁上的某個偏移量的指改為什麼,是具體到實體結構上了,比如redo log就是實體日志。
- 而邏輯日志隻是記錄了某條資料的資訊是怎麼樣的,沒有到具體的實體磁盤上
InnoDB對undo log的管理采用段的方式,也就是復原段(rollback segment) 。每個復原段記錄了 1024 個 undo log segment ,每個事務隻會使用一個復原段,當一個事務開始的時候,會制定一個復原段,在事務進行的過程中,當資料被修改時,原始的資料會被複制到復原段。
在MySQL5.5的時候,隻有一個復原段,那麼最大同時支援的事務數量為1024個。在MySQL 5.6開始,InnoDB支援最大 128個復原段,故其支援同時線上的事務限制提高到了 128*1024 。
- insert undo log格式
記錄的是insert 語句對應的undo log,它生成的undo log記錄格式如下圖:
- update undo log格式
記錄的是update、delete 語句對應的undo log,它生成的undo log記錄格式如下圖:
那麼上面這些生成undo log日志檔案最終是存儲在哪的呢?
這些復原段是存儲于共享表空間ibdata中。從MySQL5.6版本開始,可通過參數對rollback segment做進一步的設定。這些參數包括:
- innodb_undo_directory: 設定rollback segment檔案所在的路徑。這意味着rollback segment可以存放在共享表空間以外的位置,即可以設定為獨立表空間。該參數的預設值為“/”,表示目前noDB存儲引擎的目錄。
- innodb_undo_logs: 設定rollback segment的個數,預設值為128。
- innodb_undo_tablespaces: 設定構成rollback segment檔案的數量,這樣rollback segment可以較為平均地分布在多個檔案中。設定該參數後,會在路徑innodb_undo_directory看到undo為字首的檔案,該檔案就代表rollback segment檔案。
事務復原機制
對于InnoDB引擎來說,每個行記錄除了記錄本身的資料之外,還有幾個隐藏的列:
- DB_ROW_ID:如果沒有為表顯式的定義主鍵,并且表中也沒有定義唯一索引,那麼InnoDB會自動為表添加一個row_id的隐藏列作為主鍵。
- DB_TRX_ID:每個事務都會配置設定一個事務ID,當對某條記錄發生變更時,就會将這個事務的事務ID寫入tx_id
中。
- DB_ROLL_PTR: 復原指針,本質上就是指向undo log的指針。
- insert資料:
insert into user (name, sex) values('八鬥', '男')
插入的資料都會生成一條insert undo log,并且資料的復原指針會指向它。undo log會記錄undo log的序号、插入主鍵的列和值...,那麼在進行rollback的時候,通過主鍵直接把對應的資料删除即可。
- update資料
update user set sex = '男' where id = 1;
update user set name = 'alvin' where id = 1;
這時會把老的記錄寫入新的undo log,讓復原指針指向新的undo log,它的undo no是1,并且新的undo log會指向老的undo log(undo no=0),最終形成undo log版本鍊,如下圖所示:
可以發現每次對資料的變更都會産生一個undo log,當一條記錄被變更多次時,那麼就會産生多條undo log,undo log記錄的是變更前的日志,并且每個undo log的序号是遞增的,那麼當要復原的時候,按照序号依次向前推,就可以找到我們的原始資料了。
那麼按照上面的例子,事務要進行復原,最終得到下面的執行流程:
- 通過undo no=2的日志把id=1的資料的name還原成“旭陽"
- 通過undo no=1的日志把id=1的資料的sex還原成"女"
- 通過undo no=0的日志把id=1的資料根據主鍵資訊删除
undo log生命周期
生成過程
MySQL處于性能考慮,資料會優先從磁盤加載到Buffer Pool中,在更新Buffer Pool中資料之前,會優先将資料記錄到undo log中。
記錄undo log日志,MySQL不會直接去往磁盤中的xx.ibdata檔案寫資料,而是會寫在undo_log_buffer緩沖區中,因為工作線程直接去寫磁盤太影響效率了,寫進緩沖區後會由背景線程去刷寫磁盤。
删除過程
現在我們已經明白了undo log日志是如何生成,并且作用于事務復原的,那這些資料是什麼時候删除呢?
- 針對于insert undo log,因為insert操作的記錄,隻對事務本身可見,對其他事務不可見。故該undo log在事務送出後就沒有用,就會直接删除。
● 針對于update undo log,該undo log需要支援MVCC機制,是以不能在事務送出時就進行删除。送出時放入undo log連結清單,有專門的purge線程進行删除。
總結
本文詳細講解了MySQL中redo log日志的用處,以及它是如何保證事務的原子性。實際上,redo log也還有一個很重要的左右就是還對事務的隔離性實作起到作用,具體的在後面的MVCC機制中詳細說明。如果本文對你有幫助的話,請留下一個贊吧。