1. 程式人生 > 資料庫 >MySQL複習(一):MySQL基礎架構、InnoDB體系結構、MySQL日誌

MySQL複習(一):MySQL基礎架構、InnoDB體系結構、MySQL日誌

一、MySQL基礎架構

在這裡插入圖片描述

MySQL可以分為Server層儲存引擎層兩部分

Server層包括聯結器、查詢快取、分析器、優化器、執行器等,涵蓋MySQL的大多數核心服務功能,以及所有的內建函式(如日期、時間、數學和加密函式等),所有跨儲存引擎的功能都在這一層實現,比如儲存過程、觸發器、檢視等

儲存引擎層負責資料的儲存和提取。其架構模式是外掛式的,支援InnoDB、MyISAM、Memory等多個儲存引擎。現在最常用的儲存引擎是InnoDB,它從MySQL 5.5.5版本開始成為了預設儲存引擎

不同的儲存引擎共用一個Server層

1、聯結器

聯結器負責跟客戶端建立連線、獲取許可權、維持和管理連線。連線命令一般是:

mysql -h$ip -P$port -u$user -p

連線命令中的mysql是客戶端工具,用來跟服務端建立連線。在完成TCP握手後,聯結器就要開始認證身份

  • 如果使用者名稱或密碼不對,就會收到一個"Access denied for user"的錯誤,然後客戶端程式結束執行
  • 如果使用者名稱密碼認證通過,聯結器回到許可權表裡面查出你擁有的許可權。之後,這個連線裡面的許可權判斷邏輯,都將依賴於此時讀到的許可權

一個使用者成功建立連線後,即使用管理員帳號對這個使用者的許可權做了修改,也不會影響已經存在連線的許可權。修改完成後,只有再新建的連線才會使用新的許可權設定

連線完成後,如果沒有後續的動作,這個連線就處於空閒狀態,可以在show processlist命令中檢視

在這裡插入圖片描述

Command為Sleep表示此連線是一個空閒連線

客戶端如果太長時間沒動靜,聯結器就會自動將它斷開。這個時間是由引數wait_timeout控制的。預設值是8小時

如果在連線被斷開之後,客戶端再次傳送請求的話,就會收到一個錯誤提示:Lost connection to MySQL server during query。這時候就需要重新連線,然後在執行請求了

資料庫裡面,長連線是指連線成功後,如果客戶端持續有請求,則一直使用同一個連線。短連線則是指每次執行完很少的幾次查詢就斷開連線,下次查詢再重新建立一個

建立連線的過程通常是比較複雜的,所以建議儘量使用長連線

但是全部使用長連線後,有些時候MySQL佔用記憶體漲得特別快,這是因為MySQL在執行過程中臨時使用的記憶體是管理在連線物件裡面的。這些資源會在連線斷開的時候才釋放。所以如果長連線累計下來,可能導致記憶體佔用太大,被系統強行殺掉(OOM),從現象看就是MySQL異常重啟了

可以通過以下兩種方案解決這個問題:

  • 定期斷開長連線。使用一段時間,或者程式裡面判斷執行過一個佔用記憶體的大查詢後,斷開連線,之後要查詢再重連

  • 如果使用的是MySQL5.7+,可以在每次執行一個比較大的操作後,通過執行mysql_reset_connection來重新初始化連線資源。這個過程不需要重連和重新做許可權驗證,但是會將連線恢復到剛剛建立完時的狀態

2、查詢快取

MySQL拿到一個查詢請求後,會先到查詢快取看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以key-value對的形式,被直接快取在記憶體中。key是查詢的語句,value是查詢的結果。如果查詢能夠直接在這個快取中找到key,那麼這個value就會被直接返回給客戶端

如果語句不在查詢快取中,就會繼續後面的執行階段。執行完成後,執行結果會被存入查詢快取中。如果查詢命中快取,MySQL不需要執行後面的複雜操作,就可以直接返回結果

但是大多數情況下不建議使用查詢快取,因為查詢快取的失效非常頻繁,只要對一個表的更新,這個表上所有的查詢快取都會被清空。對於更新壓力大的資料庫來說,查詢快取的命中率會非常低

MySQL8.0版本直接將查詢快取的整塊功能刪掉了

3、分析器

分析器會先做詞法分析。輸入的是由多個字串和空格組成的一條SQL語句,MySQL需要識別出裡面的字串分別是什麼,代表什麼

select * from T where ID=10;

MySQL從輸入的select這個關鍵字識別出來,這是一個查詢語句。它也要把字串T識別成表名T,把字串ID識別成列ID

做完了這些識別以後,就要做語法分析。根據詞法分析的結果,語法分析器會根據語法規則,判斷這個SQL語句是否滿足MySQL語法

4、優化器

優化器是在表裡面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯的時候,決定各個表的連線順序

5、執行器

執行器階段,開始執行語句。開始執行的時候,要先判斷一下你對這個表T有沒有執行查詢的許可權,如果沒有,就會返回沒有許可權的錯誤;如果有許可權,就開啟表繼續執行

select * from T where ID=10;

開啟表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的介面

比如在表T中,ID欄位沒有索引,那麼執行器的執行流程是這樣的:

1)呼叫InnoDB引擎介面取這個表的第一行,判斷ID值是不是10,如果不是則跳過;如果是則將這個行存在結果集中

2)呼叫引擎介面取下一行,重複相同的判斷邏輯,直到取到這個表的最後一行

3)執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端

二、InnoDB體系結構

1、後臺執行緒

InnoDB儲存引擎是多執行緒的模型

1)、Master Thread

Master Thread主要負責將快取池中的資料非同步重新整理到磁碟,保證資料的一致性,包括髒頁的重新整理、合併插入緩衝(insert buffer)、undo頁的回收等

2)、IO Thread

在InnoDB中大量使用了AIO來處理寫IO請求,而IO Thread的工作主要是負責這些IO請求的回撥處理。IO Thread分為write、read、insert buffer和log IO thread四種

3)、Purge Thread

事務被提交後,其所使用的undolog可能不再需要,因此需要Purge Thread來回收已經使用並分配的undo頁

4)、Page Cleaner Thread

Page Cleaner Thread負責重新整理髒頁

2、記憶體

1)、緩衝池(Buffer Pool)

InnoDB儲存引擎是基於磁碟儲存的,而緩衝池是一塊記憶體區域,通過記憶體的速度來彌補磁碟速度較慢對資料庫效能的影響

在資料庫中進行讀取頁的操作,首先將從磁碟讀到的頁存放在緩衝池中。下一次再讀相同的頁時,首先判斷該頁是否在緩衝池中。若在緩衝池中,稱該頁在緩衝池中被命中,直接讀取該頁。否則,讀取磁碟上的頁

對於資料庫中頁的修改操作,則首先修改在緩衝池中的頁,然後再以一定的頻率重新整理到磁碟上。頁從緩衝池重新整理回磁碟的操作並不是在每次頁發生更新時觸發,而是通過一種稱為Checkpoint的機制重新整理回磁碟

在這裡插入圖片描述

緩衝池中快取的資料頁型別有:索引頁、資料頁、undo頁、插入緩衝(insert buffer)、自適應雜湊索引、InnoDB儲存的鎖資訊、資料字典資訊等

2)、LRU List、Free List和Flush List

1)LRU List

InnoDB緩衝池的記憶體管理使用最近最少使用(LRU)演算法並在此基礎上做了優化:

在這裡插入圖片描述

在InnoDB實現上,按照5:3的比例把整個LRU連結串列分成了young區域和old區域。圖中LRU_old指向的就是old區域的第一個位置,是整個連結串列的5/8處。也就是說,靠近連結串列頭部的5/8是young區域,靠近連結串列尾部的3/8是old區域

1.上圖中狀態1,要訪問資料頁P3,由於P3在young區域,因此和優化前的LRU演算法一樣,將其移到連結串列頭部,變成狀態2

2.之後要訪問一個新的不存在於當前連結串列的Pm,但是新插入的資料頁Px,是放在LRU_old處

3.處於old區域的資料頁,每次被訪問的時候都要做下面這個判斷:

  • 若這個資料頁在LRU連結串列中存在的時間超過了1秒,就把它移動到連結串列頭部
  • 如果這個資料頁在LRU連結串列中存在的時間短於1秒,位置保持不變。1秒這個時間,是由引數innodb_old_blocks_time控制的。預設值是1000,單位是毫秒

這個優化策略就是為了處理類似全表掃描的操作量身定製的:

1.掃描過程中,需要新插入的資料頁,都被放到old區域

2.一個數據頁裡面有多條記錄,這個資料頁會被多次訪問到,但由於是順序掃描,這個資料頁第一次被訪問和最後一次被訪問的時間間隔不會超過1秒,因此還是會被保留在old區域

3.再繼續掃描後續的資料,之前的這個資料頁之後也不會再被訪問到,於是始終沒有機會移到連結串列頭部,很快就會被淘汰出去

這個優化策略最大的收益就是在掃描大表的過程中,雖然也用到了緩衝池,但是對young區域完全沒有影響,從而保證了緩衝池響應正常業務的查詢命中率

2)Free List:

當需要從緩衝池中分頁時,首先從Free列表中查詢是否有可用的空閒頁,若有則將該頁從Free列表中刪除,放入到LRU列表中。否則,根據LRU演算法,淘汰LRU列表末尾的頁,將該記憶體空間分配給新的頁

3)Flush List:

在LRU列表中的頁被修改後,稱該頁為髒頁,即緩衝池中的頁和磁碟上的頁的資料產生了不一致。這時資料庫會通過Checkpoint機制將髒頁重新整理回磁碟,而Flush列表中的頁即為髒頁列表

髒頁既存在於LRU列表中,也存在於Flush列表中。LRU列表用來管理緩衝池中頁的可用性,Flush列表用來管理獎頁重新整理回磁碟,二者互不影響

3、redo log buffer(重做日誌緩衝)

InnoDB首先將redo log放入到redo log buffer,然後按一定頻率將其重新整理到redo log檔案

下列三種情況下會將redo log buffer重新整理到redo log file:

  • Master Thread每一秒將redo log buffer重新整理到redo log file
  • 每個事務提交時會將redo log buffer重新整理到redo log file
  • 當redo log緩衝池剩餘空間小於1/2時,會將redo log buffer重新整理到redo log file

4、額外的記憶體池

在對一些資料結構本身的記憶體進行分配時,需要從額外的記憶體池中進行申請,當該區域的記憶體不夠時,會從緩衝池中進行申請。例如,分配了緩衝池,但是每個緩衝池中的幀緩衝還有對應的緩衝控制物件,這些物件記錄了一些諸如LRU、鎖、等待等資訊,而這個物件的記憶體需要從額外記憶體池中申請

三、MySQL日誌

1、 redo log(重做日誌)

MySQL裡常說的WAL技術,全稱是Write Ahead Log,即當事務提交時,先寫redo log,再修改頁。也就是說,當有一條記錄需要更新的時候,InnoDB會先把記錄寫到redo log裡面,並更新Buffer Pool的page,這個時候更新操作就算完成了

Buffer Pool是物理頁的快取,對InnoDB的任何修改操作都會首先在Buffer Pool的page上進行,然後這樣的頁面將被標記為髒頁並被放到專門的Flush List上,後續將由專門的刷髒執行緒階段性的將這些頁面寫入磁碟

InnoDB的redo log是固定大小的,比如可以配置為一組4個檔案,每個檔案的大小是1GB,迴圈使用,從頭開始寫,寫到末尾就又回到開頭迴圈寫

在這裡插入圖片描述

Write Pos是當前記錄的位置,一邊寫一邊後移,寫到第3號檔案末尾後就回到0號檔案開頭。Check Point是當前要擦除的位置,也是往後推移並且迴圈的,擦除記錄前要把記錄更新到資料檔案

Write Pos和Check Point之間空著的部分,可以用來記錄新的操作。如果Write Pos追上Check Point,這時候不能再執行新的更新,需要停下來擦掉一些記錄,把Check Point推進一下

當資料庫發生宕機時,資料庫不需要重做所有的日誌,因為Check Point之前的頁都已經重新整理回磁碟,只需對Check Point後的redo log進行恢復,從而縮短了恢復的時間

當緩衝池不夠用時,根據LRU演算法會溢位最近最少使用的頁,若此頁為髒頁,那麼需要強制執行Check Point,將髒頁重新整理回磁碟

2、binlog(歸檔日誌)

MySQL整體來看就有兩塊:一塊是Server層,主要做的是MySQL功能層面的事情;還有一塊是引擎層,負責儲存相關的具體事宜。redo log是InnoDB引擎特有的日誌,而Server層也有自己的日誌,稱為binlog

binlog記錄了對MySQL資料庫執行更改的所有操作,不包括SELECT和SHOW這類操作,主要作用是用於資料庫的主從複製及資料的增量恢復

使用mysqldump備份時,只是對一段時間的資料進行全備,但是如果備份後突然發現數據庫伺服器故障,這個時候就要用到binlog的日誌了

binlog格式有三種:STATEMENT,ROW,MIXED

1)、STATEMENT模式

binlog裡面記錄的就是SQL語句的原文。優點是並不需要記錄每一行的資料變化,減少了binlog日誌量,節約IO,提高效能。缺點是在某些情況下會導致master-slave中的資料不一致

2)、ROW模式

不記錄每條SQL語句的上下文資訊,僅需記錄哪條資料被修改了,修改成什麼樣了,解決了STATEMENT模式下出現master-slave中的資料不一致。缺點是會產生大量的日誌,尤其是alter table的時候會讓日誌暴漲

3)、MIXED模式

以上兩種模式的混合使用,一般的複製使用STATEMENT模式儲存binlog,對於STATEMENT模式無法複製的操作使用ROW模式儲存binlog,MySQL會根據執行的SQL語句選擇日誌儲存方式

3、redo log和binlog的不同

  • redo log是InnoDB引擎特有的;binlog是MySQL的Server層實現的,所有引擎都可以使用
  • redo log是物理日誌,記錄的是在某個資料也上做了什麼修改;binlog是邏輯日誌,記錄的是這個語句的原始邏輯,比如給ID=2這一行的c欄位加1
  • redo log是迴圈寫的,空間固定會用完;binlog是可以追加寫入的,binlog檔案寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌

4、兩階段提交

create table T(ID int primary key,c int);
update T set c=c+1 where ID=2;

執行器和InnoDB引擎在執行這個update語句時的內部流程:

1.執行器先找到引擎取ID=2這一行。ID是主鍵,引擎直接用樹搜尋找到這一行。如果ID=2這一行所在的資料也本來就在記憶體中,就直接返回給執行器;否則,需要先從磁碟讀入記憶體,然後再返回

2.執行器拿到引擎給的行資料,把這個值加上1,得到新的一行資料,再呼叫引擎介面寫入這行新資料

3.引擎將這行新資料更新到記憶體中,同時將這個更新操作記錄到redo log裡面,此時redo log處於prepare狀態。然後告知執行器執行完成了,隨時可以提交事務

4.執行器生成這個操作的binlog,並把binlog寫入磁碟

5.執行器呼叫引擎的提交事務介面,引擎把剛剛寫入的redo log改成提交狀態,更新完成

update語句的執行流程圖如下,圖中淺色框表示在InnoDB內部執行的,深色框表示是在執行器中執行的

在這裡插入圖片描述

將redo log的寫入拆成了兩個步驟:prepare和commit,這就是兩階段提交