MySQL实战—更新过程
本文屬于個人備忘錄,主要是極客時間《MySQL實戰(zhàn)45講》學(xué)習(xí)筆記。
MySQL實戰(zhàn)—更新過程
一條查詢語句的執(zhí)行過程一般是經(jīng)過連接器、分析器、優(yōu)化器、執(zhí)行器等功能模塊,最后到達(dá)存儲引擎。那么更新語句又是如何執(zhí)行?
和查詢流程不同的是,更新流程涉及兩個重要的日志模塊:redo log(重做日志)和 binlog(二進(jìn)制日志)。
redo log
redo log通常是物理日志,記錄的是數(shù)據(jù)頁的物理修改,而不是某一行或某幾行的修改,它用來恢復(fù)提交后的物理數(shù)據(jù)頁(恢復(fù)數(shù)據(jù)頁,且只能恢復(fù)到最后一次提交的位置)。
如果MySQL每一次的更新操作都需要寫進(jìn)磁盤,整個過程IO成本會很高。使用redo log能提升更新效率,即WAL技術(shù)。
WAL
- Write-Ahead Logging
- 先寫日志,再寫磁盤
更新記錄
- InnoDB引擎先把記錄寫到redo log里面,并更新內(nèi)存
- InnoDB會在適當(dāng)?shù)臅r候,比如系統(tǒng)空閑時,將操作記錄更新到磁盤里
在相同的數(shù)據(jù)量下,采用WAL的數(shù)據(jù)庫系統(tǒng)在事務(wù)提交時,磁盤寫操作只有傳統(tǒng)的回滾日志的一半左右,大大提高了數(shù)據(jù)庫磁盤IO操作的效率,從而提高了數(shù)據(jù)庫的性能。
redo log
redo log大小固定,可配
- 是InnoDB引擎的日志
- 比如一組4個文件,每個文件大小1GB
- 從頭開始寫,寫到末尾又回到開始循環(huán)寫
- write pos是當(dāng)前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭
- checkpoint是當(dāng)前要擦除的位置,也是往后推移并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件
- write pos和checkpoint之間是可寫部分,用來記錄新的操作
- 如果write pos追上Checkpoint,表示沒有可寫位置了,此時不能執(zhí)行新的更新,需要停下來擦掉一些記錄(將記錄更新到磁盤),將checkpoint向前推進(jìn)
crash-safe
- 通過redo log,InnoDB保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失,這個能力稱為crash-safe
更新過程
以下述操作為例。
mysql> create table T(ID int primary key, c int); mysql> update T set c=c+1 where ID=2;-
執(zhí)行器通過引擎取ID=2這一行。
- ID是主鍵,引擎直接用樹搜索找到這一行
- 如果ID=2這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器;
- 否則,需要先從磁盤讀入內(nèi)存,然后再返回。
- 執(zhí)行器將引擎返回的行數(shù)據(jù)的這個值加1,即N變成N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入新行。
- 引擎將這行新數(shù)據(jù)更新到內(nèi)存中,同時將這個更新操作記錄到redo log里,此時redo log處于prepare狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時可以提交事務(wù)。
- 執(zhí)行器生成這個操作的binlog,并把binlog寫入磁盤。
- 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛寫入的redo log改成提交(commit)狀態(tài),更新完成。
執(zhí)行流程如下圖。淺色步驟在InnoDB內(nèi)部執(zhí)行,深色步驟在執(zhí)行器中執(zhí)行。
binlog
binlog,即二進(jìn)制日志,是一個二進(jìn)制文件,記錄了對數(shù)據(jù)庫執(zhí)行更新的所有操作,并且記錄了語句發(fā)生時間、執(zhí)行時長、操作數(shù)據(jù)等信息。但不記錄SELECT、SHOW等查詢SQL語句。
二進(jìn)制日志主要用于數(shù)據(jù)恢復(fù)和主從復(fù)制,及審計操作。
- max_binlog_size:日志文件大小上限,二進(jìn)制日志文件后綴名會由 mysql 自動拼接數(shù)字,達(dá)到此參數(shù)設(shè)置大小則寫入另一個文件,同時后綴 + 1,所以在設(shè)置 log-bin 參數(shù)時僅填寫路徑和文件名即可,后綴名省略
是否開啟binlog
log-bin:設(shè)置日志文件的位置,設(shè)置此參數(shù)同時開啟日志記錄,默認(rèn)放在 mysql data目錄下
mysql> show variables like 'log_bin'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | log_bin | ON | +---------------+-------+ 1 row in set (0.02 sec)查看binlog
binlog不能直接查看。可以通過官方提供的mysqlbinlog工具查看。
- 確認(rèn)日志位置
- 輸出文件內(nèi)容
如果執(zhí)行過程出現(xiàn)以下提示,可暫時添加--no-defaults參數(shù)跳過檢查。
mysqlbinlog:[ERROR] unknown variable 'default-character-set=utf8'更多內(nèi)容,請參考https://dev.mysql.com/doc/refman/5.7/en/binary-log.html
兩階段提交
寫入redo log分為兩個步驟
- prepare
- commit
即,兩階段提交。
為什么需要兩階段提交?
redo log和binlog是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完redo log再寫binlog,要么反過來。
以上述更新為例。假設(shè)當(dāng)前ID=2,字段c的值為0,并假設(shè)執(zhí)行update過程中寫完第一個日志后,發(fā)生了crash。看看兩種方式會發(fā)生什么情況。
-
先寫redo log后寫binlog
- 假設(shè)redo log寫完,binlog沒有寫完的時候,MySQL 進(jìn)程異常重啟
- redo log寫完之后,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來,所以恢復(fù)后這一行c的值是 1
- 但由于binlog沒寫完就crash了,這時候binlog里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的binlog里面就沒有這條語句。
- 如果需要用這個binlog來恢復(fù)臨時庫的話,由于這個語句的binlog丟失,這個臨時庫就會少了這一次更新,恢復(fù)出來的這一行c的值就是0,與原庫的值不同。
- 出現(xiàn)不一致
-
先寫binlog后寫redo log
- 假設(shè)binlog寫完之后,redo log沒有寫完的時候crash
- 由于redo log還沒寫,崩潰恢復(fù)以后這個事務(wù)無效,所以這一行c的值是0。
- 但是 binlog 里面已經(jīng)記錄了“把c從0改成1”這個日志。
- 后續(xù)用binlog恢復(fù)的時候就多了一個事務(wù)出來,恢復(fù)出來的這一行c的值就是 1,與原庫的值不同。
- 也出現(xiàn)不一致
可以看出,如果不使用“兩階段提交”,數(shù)據(jù)庫的狀態(tài)有可能和用binlog恢復(fù)出來的庫的狀態(tài)不一致。
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的MySQL实战—更新过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ClickHouse内核分析-Merge
- 下一篇: 容器环境自建数据库、中间件一键接入阿里云