MySQL必知必會:簡介undo log、truncate、以及undo log如何幫你回滾事物

賜我白日夢發表於2020-11-30

一、前言

在整理undo log筆記前我感覺它應該是在 undo、redo、bin log三者中需要整理的內容最少的。但是實際上並不是想象的那麼簡單。

關於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記錄到共享表空間中。

MySQL預設的配置檔案 my.cnf 長下面這樣:

如果你現在僅僅是安裝了MySQL,而不曾啟動過mysql,那你去datadir中檢視會發現它只是個空目錄。

但是當你啟動過MySQL之後,再去這個datadir中檢視會發現裡面多了很多檔案,其中就包括共享表空間檔案ibdata1(但是沒有undolog表空間檔案)。如下:


四、如何將undo log放到單獨的表空間

如果你想將undo log拿到undo log表空間檔案中。那你可以像下面這樣修改MySQL的配置檔案my.cnf

修改完後通過如下命令啟動mysql

systemctl start mysqld.service

但是你會發現啟動不了,如果你去排查原因就會發現:因為曾經初始化過 datadir 目錄中的檔案,你新增的新配置innodb_undo_tablespaces和原來的配置是衝突的,需要開闢新的表空間檔案,所以導致啟動失敗。

解決的方式:簡單粗暴的將換個datadir檔案就好啦,所以如果你從一開始就想將undolog拿到單獨的表空間中,那麼最好從一開始就將這個配置新增進去,否則還是挺麻煩的。


本文是MySQL專題第14篇,全文近100篇

本文是第14篇,全文近100篇,點選檢視目錄


五、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表空間

然後配合如下的引數輔助:

建立資料表:

create table test (
	id int primary key auto_increment, 
	name varchar(64)
);

然後不斷的往這個測試表中插入資料

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已經被標記為可回收了。

當事物提交時,undo log並不會被立即刪除,因為可能存在其它的事物需要使用undo log將資料回滾到之前的版本。最終是否可以刪除undo log由purge執行緒決定。

為了讓pruge執行緒執行,可以執行如下的sql

delete from test limit 1;


八、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長下面這樣:

可能你打眼一看上圖就能知道各部分都有啥用。

但是,不知道你會不會納悶這樣一個問題:不是說對於insert 型別的undo log MySQL記錄的是方才插入行ID嗎?怎麼上圖整出來的了這麼多Col1、Col2、Col2。

其實是MySQL設計的很周到,因為它是針對聯合主鍵設計的。


十、一條update undo log 長啥樣?

一條update sql對應undolog長如下這樣。

其實我感覺沒必要記住這個圖,記住了也會忘。大概看一下它長什麼樣子就好。

重點是下面會分享的,undo log鏈條,並且你得知道這個鏈條可以幫你實現事務的回滾


十一、事物是如何回滾的?(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 鏈條

有一個注意點:因為單純的insert sql不涉及多MVCC的能力。

所以一旦事務commit,這條insert undo log就可以直接刪除了。

再看一個update型別的undo log

為了方便畫圖,重點突出鏈條的概念我省略了update undo log的部分內容

一個事物A開啟後插圖了一條記錄:name = tom,MySQL會記錄下這樣一條undo log

隨後先後來了兩個事物:

事物B,事物ID=61,它執行sql將name 改成jerry。

事物C,事物ID=62,它執行sql將name 改成tom。

於是MySQL記錄下這樣一條新的undo log

你可以看到,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篇,點選檢視目錄




相關文章