天天看點

MySQL學習筆記(二)一條SQL更新語句

一條更新語句的執行流程 

從一個表的一條更新語句說起,下面是這個表的建立語句,這個表有一個主鍵ID和一個整型字段c

mysql> create table T(ID int primary key, c int);

假如将ID=2這一行的值加1

mysql> update T set c=c+1 where ID=2;

MySQL學習筆記(二)一條SQL更新語句

執行語句要先通過連接配接器連接配接資料庫。在一個表上更新的時候,跟這個表有關的查詢緩存會失效。

接下來分析器會通過詞法和文法解析知道這是一條更新語句。優化器決定要使用ID這個索引。然後,執行器負責執行,找到這一行然後更新。

與查詢不同,更新和涉及到兩個重要的日志子產品:redo log(重做日志)和binlog(歸檔日志)。

redo log 重做日志

WAL技術(Write-Ahead Logging):先寫日志,再寫磁盤。因為如果每次都直接寫磁盤的話會效率很低很慢,是以等不忙的時候再寫入磁盤。

具體,就是當有一條記錄需要更新的時候,InnoDB引擎會先把記錄寫到redo log裡面,并更新記憶體。這個時候更新算完成。然後引擎會在适當的時候,再将這個操作記錄更新到磁盤裡,這時往往是系統比較空閑的時候。

但是redo log是固定的大小。有時候,假如記憶體太滿,就必須先将一部分記憶體更新到磁盤,再将這部分擦掉,然後在記憶體中寫入新的操作

MySQL學習筆記(二)一條SQL更新語句

假如說可以配置為一組4個檔案,每個大小1G,那麼總共可以記錄4GB的操作。write pos是目前記錄的位置,一邊寫一邊後移,寫到第3号檔案末尾就回到了0号檔案開頭。checkpoint是目前要擦除的位置,也是往後推移并循環。

write pos和checkpoint之間是還空着的部分,可以記錄新的操作。如果write pos追上checkpoint,這時代表redo log已滿,得先擦掉一些記錄,把checkpoint推進一下。

redo log可以使資料庫即使發生異常重新開機,之前送出的記錄也不會丢失,這稱為crash-safe。

binlog 歸檔日志

redo log是InnoDB引擎特有的日志,而Server層自己的日志稱為binlog,所有引擎都可使用。

redo log是實體日志,記錄“在某資料頁做了什麼修改”。binlog是邏輯日志,記錄這個語句的原始邏輯,“給ID=2這行的c字段加1”

redo log是循環寫,空間固定會用完。binlog是追加寫入,指檔案寫到一定大小會切換到下一個,并不會覆寫。

接下來是更新流程:

1.執行器先找到引擎取ID=2這一行。ID是主鍵,引擎通過樹搜尋找到這一行。如果這一行的資料頁本來就在記憶體,就直接傳回給執行器;否則需要先從磁盤讀入記憶體,再傳回。

2.執行器給這個值加1,再調用引擎接口寫入這行新資料。

3.引擎将這行資料更新到記憶體,同時将這個更新操作記錄到redo log。此時redo log會處于prepare狀态,告知執行器執行完成,随時可送出。

4.執行器生成這個操作的binlog,并把binlog寫入磁盤。

5.執行器調用引擎送出的事務接口,引擎把剛剛寫入的redo log改為commit狀态,更新完成。

MySQL學習筆記(二)一條SQL更新語句

(淺色為InnoDB内部執行,深色表示執行器中執行)

redo log的寫入在最後被binlog拆為兩個步驟:prepare和commit,這就是兩階段送出。

兩階段送出

怎麼讓資料庫恢複到半個月内任意一秒的狀态?

這意味着備份系統中儲存着最近半個月所有的binlog,同時系統會做定期的整庫備份。可以一天一備,也可一周一備。

一天一備對比一周一備的好處是“最長恢複時間”更短。最壞的情況是需要應用一天的binlog。比如每天0點做全量備份,而要恢複出一個到昨晚23點的備份。一周一備最壞的情況就是用一周的binlog。

當然更頻繁的全量備份需要消耗更多存儲空間。需要根據業務重要性來評估。

比如發現某天下午兩點發現中午十二點有一次誤删表,需要找回資料,那可以:

首先找到最近一次全庫備份,如果運氣好就是昨晚一個備份,從這個備份恢複到臨時庫。

然後從備份的時間點開始,将備份的binlog依次取出,重放到中午誤删那個時刻。

然後就可以将表資料從臨時庫取出來,按需要恢複到線上庫。

假如不是兩階段:

1.先寫redo log後寫binlog。假設在redo log寫完還沒寫binlog,也就是redo log寫完即使崩潰,仍然能夠把資料庫恢複回來,這一行的c就是加完後的1。但是binlog沒有寫完,存起來的binlog就沒有這條語句。如果後來用binlog恢複臨時庫,由于這句缺失,導緻臨時庫恢複的這行就是0,與原庫不同。

2.先寫binlog後寫redo log。假如binlog寫完後crash,redo log還沒寫,崩潰後恢複這行c還是0。但是binlog裡面已經記錄了把c從0改為1這個日志,是以之後用binlog來恢複就多出一個事務,恢複的c就是1,與原庫不同。

可以看到如果不是兩階段送出,那麼資料庫的狀态可能就和用它的日志恢複出來的不一緻。

兩階段送出就是讓這兩個狀态保持邏輯上的一緻。