密云做網(wǎng)站百度開戶公司
?? ?Server 層
?? ?存儲引擎層
?? ?總流程
?? ??? ?查詢語句
?? ??? ??? ?連接器
?? ??? ??? ?查詢緩存
?? ??? ??? ?分析器
?? ??? ??? ?優(yōu)化器
?? ??? ??? ?執(zhí)行器
?? ??? ?更新語句
?? ??? ??? ?redo log(節(jié)省的是隨機寫磁盤的 IO 消耗(轉成順序寫))
?? ??? ??? ??? ?InnoDB 引擎
?? ??? ??? ??? ?物理日志
?? ??? ??? ??? ?循環(huán)寫
?? ??? ??? ?binlog
?? ??? ??? ??? ?server層
?? ??? ??? ??? ?邏輯日志
?? ??? ??? ??? ?追加寫入
?? ??? ??? ?兩階段提交
為了知道,mysql 中一條 sql 語句是如何執(zhí)行的,先講一下 mysql 的宏觀分層以及如何執(zhí)行查詢語句.
?MySQL 宏觀上分為 Server 層 和 存儲引擎層, MySQL 通過 server 層調(diào)用存儲引擎層操作數(shù)據(jù)返回結果,Server 層又可以分為
- 連接器
- 分析器
- 優(yōu)化器
- 執(zhí)行器
連接器負責跟客戶端建立連接、獲取權限、維持和管理連接。
分析器先會做“詞法分析”。你輸入的是由多個字符串和空格組成的一條 SQL 語句,MySQL 需要識別出里面的字符串分別是什么,代表什么。做完了這些識別以后,就要做“語法分析”。根據(jù)詞法分析的結果,語法分析器會根據(jù)語法規(guī)則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法。
優(yōu)化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(lián)(join)的時候,決定各個表的連接順序。?
執(zhí)行器會根據(jù)表的引擎定義,去使用這個引擎提供的接口。示例:
mysql> select * from T where ID=10;
比如我們這個例子中的表 T 中,ID 字段沒有索引,那么執(zhí)行器的執(zhí)行流程是這樣的:
- 調(diào)用 InnoDB 引擎接口取這個表的第一行,判斷 ID 值是不是 10,如果不是則跳過,如果是則將這行存在結果集中;
- 調(diào)用引擎接口取“下一行”,重復相同的判斷邏輯,直到取到這個表的最后一行。
- 執(zhí)行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端。
至此,這個語句就執(zhí)行完成了。
查詢語句的那一套流程,更新語句也是同樣會走一遍。與查詢流程不一樣的是,更新流程還涉及兩個重要的日志模塊,它們正是我們今天要討論的主角:redo log(重做日志)和 binlog(歸檔日志)。
redo log
如果每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高。
當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log里面,并更新內(nèi)存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當?shù)臅r候,將這個操作記錄更新到磁盤里面。
InnoDB 的 redo log 是固定大小的,滿了時不能再執(zhí)行新的更新。由參數(shù) innodb_log_file_size 和?innodb_log_files_in_group 決定。
有了 redo log,InnoDB 就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe。?
change buffer
當需要更新一個數(shù)據(jù)頁時,如果數(shù)據(jù)頁在內(nèi)存中就直接更新,而如果這個數(shù)據(jù)頁還沒有在內(nèi)存中的話,InnoDB 會將這些更新操作緩存在 change buffer 中,這樣就不需要從磁盤中讀入這個數(shù)據(jù)頁了。在下次查詢需要訪問這個數(shù)據(jù)頁的時候,將數(shù)據(jù)頁讀入內(nèi)存,然后執(zhí)行 change buffer 中與這個頁有關的操作。change buffer 在內(nèi)存中有拷貝,也會被寫入到磁盤上。
將 change buffer 中的操作應用到原數(shù)據(jù)頁,得到最新結果的過程稱為 merge。除了訪問這個數(shù)據(jù)頁會觸發(fā) merge 外,系統(tǒng)有后臺線程會定期 merge。在數(shù)據(jù)庫正常關閉(shutdown)的過程中,也會執(zhí)行 merge 操作。
change buffer 用的是 buffer pool 里的內(nèi)存,因此不能無限增大。change buffer 的大小,可以通過參數(shù) innodb_change_buffer_max_size 來動態(tài)設置。這個參數(shù)設置為 50 的時候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%。
因為 merge 的時候是真正進行數(shù)據(jù)更新的時刻,而 change buffer 的主要目的就是將記錄的變更動作緩存下來,所以在一個數(shù)據(jù)頁做 merge 之前,change buffer 記錄的變更越多(也就是這個頁面上要更新的次數(shù)越多),收益就越大。因此,對于寫多讀少的業(yè)務來說,頁面在寫完以后馬上被訪問到的概率比較小,此時 change buffer 的使用效果最好。?
binlog
redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志).這兩種日志有以下三點不同。
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現(xiàn)的,所有引擎都可以使用。
- redo log 是物理日志,記錄的是“在某個數(shù)據(jù)頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
- redo log 是循環(huán)寫的,空間固定會用完;binlog 是可以追加寫入的?!白芳訉憽笔侵?binlog 文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志。
執(zhí)行器和 InnoDB 引擎在執(zhí)行這個簡單的 update 語句時的內(nèi)部流程:?
- 執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤讀入內(nèi)存,然后再返回。
- 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個值加上 1,比如原來是 N,現(xiàn)在就是 N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù)。
- 引擎將這行新數(shù)據(jù)更新到內(nèi)存中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時可以提交事務。
- 執(zhí)行器生成這個操作的 binlog,并把 binlog 寫入磁盤。
- 執(zhí)行器調(diào)用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成。
將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。?
推薦閱讀
一條 SQL 更新語句如何執(zhí)行的?
MySQL 事務的原理以及長事務的預防和處置?
InnoDB索引優(yōu)化?
一條 sql 語句可能導致的表鎖和行鎖以及死鎖檢測?