MySQL必知必會:簡介undo log、truncate、以及undo log如何幫你回滾事務
1、簡介undo log、truncate、以及undo log如何幫你回滾事物(本篇分享)
2、undolog鏈條、ReadView、以及undo log如何幫你實現MVCC多版本併發控制(明天分享)
#### 二、undo log表空間
如果你看了白日夢前面的分享的筆記,你肯定知道了什麼表空間。其實所謂的表空間其實是真實存在於磁碟上的資料檔案。而這裡的所說的undolog表空間其實就是磁碟上專門存放undo log的檔案。
表空間由很多 segment(段) 組成,而這眾多的段中有一種就是 undo segment。
預設情況下undo segment 會存放於系統表空間中,或者說undo log預設會記錄在共享表空間檔案中(檔案真實存在)。
但是MySQL也提供了引數,讓你可以控制MySQL講undo log寫入到單獨的表空間檔案中去。尤其是當你使用SSD這種儲存時,尤為推薦將undo log從共享表空間中拿出去。
#### 三、關於undo log預設的配置
預設情況下undo log tablespace個數是0,也就是說如果你不干涉MySQL的配置。那麼MySQL就會幫你將undo log記錄到共享表空間中。
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135343845-1956461503.png)
MySQL預設的配置檔案 my.cnf 長下面這樣:
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135344993-1010751666.png)
如果你現在僅僅是安裝了MySQL,而不曾啟動過mysql,那你去datadir中檢視會發現它只是個空目錄。
但是當你啟動過MySQL之後,再去這個datadir中檢視會發現裡面多了很多檔案,其中就包括共享表空間檔案ibdata1(但是沒有undolog表空間檔案)。如下:
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135346220-1176247063.png)
#### 四、如何將undo log放到單獨的表空間
如果你想將undo log拿到undo log表空間檔案中。那你可以像下面這樣修改MySQL的配置檔案my.cnf
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135347713-1352405584.png)
修改完後通過如下命令啟動mysql
```bash
systemctl start mysqld.service
```
但是你會發現啟動不了,如果你去排查原因就會發現:因為曾經初始化過 datadir 目錄中的檔案,你新增的新配置innodb_undo_tablespaces和原來的配置是衝突的,需要開闢新的表空間檔案,所以導致啟動失敗。
解決的方式:簡單粗暴的將換個datadir檔案就好啦,所以如果你從一開始就想將undolog拿到單獨的表空間中,那麼最好從一開始就將這個配置新增進去,否則還是挺麻煩的。
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135348792-1051464316.png)
#### 本文是MySQL專題第14篇,全文近100篇
本文是第14篇,全文近100篇,[點選檢視目錄](https://www.cnblogs.com/ZhuChangwu/p/13953209.html)
#### 五、rollback segment
提到了undo log,就不得不說roll back segment這個知識點了。它並不難理解,你可以閱讀下面的介紹瞭解一下。
InnoDB儲存引擎會先初始化好rollback segment(回滾段),在每個回滾段中會記錄N個undo log segment,而我們說的undo log就是在 undo log segment中申請出來的!
在早期的InnoDB版本中只有一個rollback segment,因此在同一時刻它支援的線上事物的上限被限制在1024個。
在MySQL5.7中回滾段已經支援到了128個(上限是128)。其中32個分配給臨時表空間。剩下的96個回滾段可以分配給修改常規表中資料的事務。
使用者可以通過引數`innodb_rollback_segments`調整回滾段的數量。
另外,我們上面提到的: 每個回滾段中都記錄了N個undolog segment, 這裡的N和資料頁大小有關
| InnoDB頁面大小 | 回滾段中的撤消插槽數(InnoDB頁面大小/ 16) |
| :------------- | :----------------------------------------- |
| 4096 (4KB) | 256 |
| 8192 (8KB) | 512 |
| 16384 (16KB) | 1024 |
| 32768 (32KB) | 204 |
| 65536 (64KB) | 4096 |
#### 六、什麼是undo log truncate
truncate意為:截斷
其實結合 truncate table sql,就能更好的理解這個概念。當你不需要某個表中的資料時,你可以執行truncate sql將表中的資料清空掉。同樣的undo log的truncate機制本質上就是為undo log 表空間檔案瘦身,將不需要的undo log清理掉。
在MySQL 5.6(包括5.6)之前Undo tablespace裡面的undo資料檔案是無法收縮的。也就是說在例項的執行過程中如果遇到有大的事務,會把undo log的檔案撐的非常大。浪費大量的空間甚至會把磁碟打爆。同時也增加了資料庫物理備份的時間。
在MySQL5.7中允許使用者線上truncate undo log
#### 七、如果做 undo log truncate
前提:必須使用獨立的undo表空間
然後配合如下的引數輔助:
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135349855-579426094.png)
建立資料表:
```bash
create table test (
id int primary key auto_increment,
name varchar(64)
);
```
然後不斷的往這個測試表中插入資料
```bash
insert into test(name) values(repeat('a',64));
insert into test(name) select name from test;
```
一邊插入一邊觀察undo 表空間檔案的變化:你會發現undo003這個表空間檔案已經超過了引數:`innodb_max_undo_log_size=100M` 指定的範圍,意味著這個undolog已經被標記為可回收了。
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135351339-191437303.png)
當事物提交時,undo log並不會被立即刪除,因為可能存在其它的事物需要使用undo log將資料回滾到之前的版本。最終是否可以刪除undo log由purge執行緒決定。
為了讓pruge執行緒執行,可以執行如下的sql
```bash
delete from test limit 1;
```
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135352540-917882408.png)
#### 八、undo log的型別
undo log有兩種型別,分別是 insert undo log 和 update undo log。
前者記錄的是insert 語句對應的undo log。
後者對應的是 update、delete 語句對應的undo log。
#### 九、insert undo log 長啥樣?
對於 insert 型別的sql,會在undo log中記錄下方才你insert 進來的資料的ID,根據ID完成精準的刪除。
insert 型別的undo log長下面這樣:
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135353227-1301497923.jpg)
可能你打眼一看上圖就能知道各部分都有啥用。
但是,不知道你會不會納悶這樣一個問題:不是說對於insert 型別的undo log MySQL記錄的是方才插入行ID嗎?怎麼上圖整出來的了這麼多Col1、Col2、Col2。
其實是MySQL設計的很周到,因為它是針對聯合主鍵設計的。
#### 十、一條update undo log 長啥樣?
一條update sql對應undolog長如下這樣。
其實我感覺沒必要記住這個圖,記住了也會忘。大概看一下它長什麼樣子就好。
> 重點是下面會分享的,undo log鏈條,並且你得知道這個鏈條可以幫你實現事務的回滾
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135354101-1982078583.jpg)
#### 十一、事物是如何回滾的?(undo log 鏈條)
舉個例子:
對於 insert 型別的sql,會在undo log中記錄下方才你insert 進來的資料的ID,當你想roll back時,根據ID完成精準的刪除。
對於delete型別的sql,會在undo log中記錄方才你刪除的資料,當你回滾時會將刪除前的資料insert 進去。
對於update型別的sql,會在undo log中記錄下修改前的資料,回滾時只需要反向update即可。
對於select型別的sql,別費心了,select不需要回滾。
先看一個簡單的insert undo log 鏈條
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135355248-220727185.png)
有一個注意點:因為單純的insert sql不涉及多MVCC的能力。
所以一旦事務commit,這條insert undo log就可以直接刪除了。
**再看一個update型別的undo log**
為了方便畫圖,重點突出鏈條的概念我省略了update undo log的部分內容
一個事物A開啟後插圖了一條記錄:name = tom,MySQL會記錄下這樣一條undo log
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135355845-138572773.jpg)
隨後先後來了兩個事物:
事物B,事物ID=61,它執行sql將name 改成jerry。
事物C,事物ID=62,它執行sql將name 改成tom。
於是MySQL記錄下這樣一條新的undo log
![](https://img2020.cnblogs.com/blog/1496926/202011/1496926-20201130135357078-895410377.png)
你可以看到,MySQL會將對一行資料的修改undo log通過DATA_ROLL_ID指標連線在一起形成一個undo log連結串列鏈條。這樣事物C如果想回滾,他會將資料回滾到事物B修改後的狀態。而事物B想回滾他會將資料回滾到事物A的狀態。
#### 十二、問個問題
> 在前面的文章中有專門的介紹:表空間、資料表、資料區、資料頁。
>
> 表空間、資料頁存在於物理層面。SQL想要修改的資料表、id=xxx的行都是邏輯上的。
而 undo log 幫你做的是邏輯上的資料回滾,而不是物理(資料頁)上是資料回滾。
其實在邏輯層和物理層都能回滾。
那,你有沒有想過為什麼undo回滾的層面要設定在邏輯層而不是物理層的資料頁級別?
原因你可以這樣想:假如一個數據頁中存了300行資料,而你的update語句其實可能僅僅是更新了這個資料頁中的一行。但是資料庫可不一定是你自己在用!很可能有其他的使用者也在使用並且修改了該資料頁中的另外200行。那這時如果你基於資料頁層面回滾,豈不是會將別人的不想回滾的資料給改錯?
#### 十三、補充:
在MySQL5.6、MySQL5.7版本中可以通過`innodb_undo_tablespaces`引數配置redo log表空間檔案的個數,但是官網也有介紹這個引數在未來的MySQL版本中將會被廢棄,在MySQL8.0中初始化MySQL例項時會建立兩個預設的撤消表空間,並且可以使用`CREATE UNDO TABLESPACE`語法建立其他撤消表空間 。
但是不管怎麼樣,如果你使用的是MySQL5.7還是推薦使用這些引數以及開啟undo log的自動truncate。
參考:
《MySQL技術內幕》
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-logs.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.html
https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_undo_tablespace
#### 本文是MySQL專題第14篇,全文近100篇
本文是第14篇,全文近100篇,[點選檢視目錄](https://www.cnblogs.com/ZhuChangwu/p/13953209.html)