MySQL 原理篇
MVCC
MVCC 的定義
MVCC(Multiversion concurrency control):多版本並發控制,並發訪問(讀或寫)數據庫時,對正在事務內處理的數據做多版本的管理。以達到用來避免寫操作的堵塞,從而引發讀操作的並發問題。
MVCC 邏輯流程
插入
MySQL 在每一行數據中都會默認添加一些隱藏列 DB_TRX_ID、DB_ROLL_PT。
上面圖中的執行步驟如下:
- 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(1)
- 然后往 teacher 表中插入兩條數據,同時設置數據行的版本號為當前事務ID,刪除版本號為 NULL
思考:如果事務是自動提交的(SET AUTOCOMMIT = NO),且未手動開啟事務,執行如下兩條 SQL,插入的數據會是什么樣子的?
INSERT INTO teacher (NAME, age) VALUE ('seven', 18) ; INSERT INTO teacher (NAME, age) VALUE ('qingshan', 19) ;
因為事務是自動提交的,所以兩條插入語句會分別獲取事務ID,所以這里插入的數據行的版本號是1和2。
刪除
上面圖中的執行步驟如下:
- 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(22)
- 然后執行一條刪除語句,InnoDB 會找到這條記錄,把它的刪除版本號設置為當前事務ID
修改
上面圖中的執行步驟如下:
- 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(33)
- 然后執行一條修改語句,InnoDB 會找到這條記錄,copy 一份原數據插入到表中,將新行數據的數據行的版本號的值設置為當前事務ID,將原行數據的刪除版本號的值設置為當前事務ID
查詢
上面圖中的執行步驟如下:
- 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(44)
- 根據數據查詢規則的描述
- 查找數據行版本早於當前事務版本的數據行,發現表中三行數據都滿足條件
- 查找刪除版本號要么為 NULL,要么大於當前事務版本號的記錄,發現只有最后一條數據滿足條件(1, seven, 19)
案例分析
數據准備:
CREATE TABLE `teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; INSERT INTO teacher(id,NAME,age) VALUES (1,'seven',18); INSERT INTO teacher(id,NAME,age) VALUES (2,'qingshan',20);
案例一
-- 事務A執行 BEGIN; -- 1 SELECT * FROM teacher; -- 2 COMMIT; --事務B執行 BEGIN; -- 3 UPDATE teacher SET age =28 WHERE id=1; -- 4 COMMIT;
案例一的執行步驟是:1,2,3,4,2,執行效果如下圖所示:
雖然在執行 3,4 步驟的時候更新 id=1 的數據,但是根據 MVCC 的查詢邏輯流程,再次執行2,獲取到的數據依然和第一次一樣。
案例二
-- 事務A執行 BEGIN; -- 1 SELECT * FROM teacher; -- 2 COMMIT; --事務B執行 BEGIN; -- 3 UPDATE teacher SET age =28 WHERE id=1; -- 4 COMMIT;
案例二的執行步驟是:3,4,1,2,執行效果如下圖所示:
根據 MVCC 的查詢邏輯流程,執行1,2,獲取到的數據是事務B未提交的數據,這個是有問題的。
分析了案例一和案例二,發現 MVCC 不能解決案例二的問題,InnoDB 會使用 Undo log 解決案例二的問題。
Undo Log
Undo Log 的定義
Undo:意為取消,以撤銷操作為目的,返回指定某個狀態的操作。
Undo Log:數據庫事務提交之前,會將事務修改數據的鏡像(即修改前的舊版本)存放到 undo 日志里,當事務回滾時,或者數據庫奔潰時,可以利用 undo 日志,即舊版本數據,撤銷未提交事務對數據庫產生的影響。。
- 對於 insert 操作,undo 日志記錄新數據的 PK(ROW_ID),回滾時直接刪除;
- 對於 delete/update 操作,undo 日志記錄舊數據 row,回滾時直接恢復;
- 他們分別存放在不同的buffer里。
Undo Log 是為了實現事務的原子性而出現的產物。
Undo Log 實現事務原子性:事務處理過程中,如果出現了錯誤或者用戶執行了 ROLLBACK 語句,MySQL 可以利用 Undo Log 中的備份將數據恢復到事務開始之前的狀態。 |
InnoDB 發現可以基於 Undo Log 來實現多版本並發控制。
Undo Log 在 MySQL InnoDB 存儲引擎中用來實現多版本並發控制。
Undo Log 實現多版本並發控制:事務未提交之前,Undo Log 保存了未提交之前的版本數據,Undo Log 中的數據可作為數據舊版本快照供其他並發事務進行快照讀。 |
分析下圖中 SQL 的執行過程。
- 事務A手動開啟事務,執行更新操作,首先會把更新命中的數據拷貝到 Undo Buffer 中
- 事務B手動開啟事務,執行查詢操作,會讀取 Undo Log 中數據返回,進行快照度
當前讀和快照讀
快照讀
SQL 讀取的數據是快照版本,也就是歷史版本,普通的 SELECT 就是快照讀。
InnoDB 快照讀,數據的讀取將由 cache(原本數據)+ Undo Log(事務修改過的數據)兩部分組成。
當前讀
SQL 讀取的數據是最新版本,通過鎖機制來保證讀取的數據無法通過其他事務進行修改。
UPDATE 、DELETE 、INSERT 、SELECT … LOCK IN SHARE MODE 、SELECT … FOR UPDATE 都是當前讀,這些操作在《MySQL InnoDB 鎖》這篇文章中有過演示,事務A執行這些 SQL,會阻塞事務B的 SQL 執行。
在 InnoDB 引擎里面,快照讀通過 MVCC 解決幻讀的問題,當前讀通過 Next-Key Locks 解決幻讀的問題。
Redo Log
Redo Log 的定義
Redo:顧名思義就是重做。以恢復操作為目的,重現操作。
Redo Log:指事務中操作的任何數據,將最新的數據備份到一個地方(Redo Log)。
Redo Log 的持久化:不是隨着事務的提交才寫入的,而是在事務的執行過程中,便開始寫入 Redo Log 中,具體的落盤策略可以進行配置。
Redo Log 是為了實現事務的持久性而出現的產物。
Redo Log 實現事務持久性:防止在發生故障的時間點,尚有臟頁未寫入表的 IBD 文件中,在重啟 MySQL 服務的時候,根據 Redo Log 進行重做,從而達到事務的未入磁盤數據進行持久化這一特性。
根據下圖分析 Redo Log 的執行流程
InnoDB 不是每一次提交事務都把數據從緩存區持久化到硬盤的,因為每次提交事務都把數據持久化到硬盤,效率很低,每一次持久化都需要執行 IO 操作。
InnoDB 會把每次數據變化會先進入 Redo Buffer 中,事務提交了,會根據策略把新的數據寫入 Redo Log 中,InnoDB 就會認為這次事務提交成功了,數據並不一定馬上就進入表的 IBD 文件中。
疑問:持久化到 Redo Log 中和持久化到表的 IBD 文件一樣都是 IO 操作,為什么要設計 Redo Log 呢?
其實是因為持久化到 Redo Log 中是順序 IO 的操作,而持久化到表的 IBD 文件中是一個隨機 IO 的操作,比如我們需要更新 id=1 和 id=8 的數據,如果是 Redo Log,就只需要把更新的數據順序存入 Redo Log 中;但如果是表的 IBD 文件,就需要先找到 id=1 和 id=8 的兩個不連續的磁盤文件地址,再做持久化操作,影響數據庫服務的並發性能。
Redo Log 的持久化配置
指定 Redo Log 記錄在 {datadir}/ib_logfile1 和 ib_logfile2 兩個文件中,可以通過 innodb_log_group_home_dir配置指定目錄存儲。
一旦事務成功提交且數據持久化到表的 IBD 文件中之后,此時 Redo Log 中的對應事務數據記錄就失去了意義,所 以 Redo Log 的寫入是日志文件循環寫入的過程,也就是覆蓋寫的過程。
- 指定 Redo Log 日志文件組中的數量 innodb_log_files_in_group 默認為2
- 指定 Redo Log 每一個日志文件最大存儲量 innodb_log_file_size 默認48M
- 指定 Redo Log 在 cache/buffer 中的 buffer 池大小 innodb_log_buffer_size 默認16M
Redo Buffer 持久化到 Redo Log 的策略,通過設置 Innodb_flush_log_at_trx_commit 的值:
- 取值0:每秒提交 Redo buffer -> Redo Log OS cache -> flush cache to disk,可能丟失一秒內的事務數據。
- 取值1(默認值):每次事務提交執行 Redo Buffer -> Redo Log OS cache -> flush cache to disk,最安全,性能最差的方式
- 取值2:每次事務提交執行 Redo Buffer -> Redo log OS cache 再每一秒執行 -> flush cache to disk 操作
一般建議選擇取值2,因為 MySQL 掛了最多損失一次事務提交的數據,整個服務期掛了才會損失一秒的事務提交數據。