想要深入的了解MySQL,首先要了解MySQL語句是怎么實現的,了解了MySQL里語句的執行過程可以更加快速的分析問題的原因,或者進行合理的優化。
MySQL的架構
MySQL的架構圖如下所示,主要由以下幾個部分組成:連接器,緩存,分析器,優化器,執行器和存儲引擎。
MySQL可以分為server層和存儲引擎層,server層包括連接器、分析器、優化器和執行器,主要負責SQL語法的解析,內置函數的實現,觸發器,視圖等。存儲引擎層負責數據的存儲和提取,存儲引擎是插件式的,MySQL支持的存儲引擎就有InnoDB、MyISAM、Memory等。目前,InnoDB是mysql默認的存儲引擎。
連接器
連接器負責與客戶端建立網絡連接、校驗用戶名密碼、校驗用戶權限、維持和管理連接等。
網絡連接建立后,首先驗證用戶名和密碼,用戶名和密碼驗證通過以后連接器會到權限表里查詢該用戶的權限。之后,這個連接里的權限判斷邏輯,都將依賴于此時讀到的權限。這就意味著,一個用戶成功連接后,再去修改該用戶的權限,也不會影響到已經建立好的連接,只有重新建立連接權限才會生效。
MySQL的網絡連接采用的是多線程模型,維護一個線程池,每當有一個新的連接請求時,就從空閑的線程池中選擇一個線程進行處理。可以使用 show processlist 命令看到當前所建立的所有連接。
+------------+--------------+--------------------+------------------+---------+-------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------------+--------------+--------------------+------------------+---------+-------+-------+------------------+ | 1801071833 | user_name | 10.1.1.1:49788 | test_db | Sleep | 131 | | NULL | | 2309292411 | user_name | 10.1.1.1:57642 | test_db | Query | 0 | NULL | show processlist | 復制代碼
ID 表示建立連接的線程 ID 。客戶端如果一段時間沒有動作, Command 一欄就會顯示Sleep,表示該連接處于空閑狀態。 多線程的模型必然存在連接數有限的問題,因此客戶端如果太長時間沒有動靜,連接器就會自動斷開,回收線程。
緩存
連接建立后,就可以執行查詢語句。查詢語句首先會查詢緩存中是否該語句的緩存結果,因為MySQL查詢語句的執行結果可能會已K-V的形式存儲在緩存中,SQL語句做KEY,查詢的結果做值。
但是MySQL自帶的緩存不建議使用,因為MySQL的緩存失效的非常頻繁,只要對一個表有更新,那么這個表上所有的緩存都會失效,因此緩存命中率很低。不如在業務層用redis或者Memcached做緩存來的靈活高效。
分析器
如果緩存沒有命中或者沒用使用緩存,查詢語句就會到達分析器,分析器就是一個編程語言的解析器,解析的是SQL語言。分析器的工作主要分為兩個部分:
1 詞法分析:詞法分析時分析器會分析SQL語句中每個用空格或者逗號分割的字符串,把SELECT關鍵詞提取出來,把語句里的標識為表名的字符串對應到MySQL的表,把每一個column對應到表里的字段。
2 語法分析:語法分析就是整個SQL語句是否滿足語法要求,滿足則能執行成功,不滿足則報錯。
優化器
優化器的功能一句話就能描述,卻非常重要,決定了查詢的性能。優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在聯表查詢時決定用哪一張表關聯哪一張表。
執行器
執行器的功能就是調用存儲引擎的API存入數據或者取出數據。在調用存儲引擎的API之前會先進行權限校驗,校驗該用戶是否有對該表相應的操作權限。存儲引擎如果索引沒有命中,存儲引擎就一條條掃表,直到查到指定的數據,然后返回給server層。如果命中了索引,存儲引擎就在索引命中的數據中一條條掃描,直到查到指定的數據。如果索引類型為const類型,那么存儲引擎會直接命中,然后返回。
查詢語句如何實現
說完了MySQL的架構,我們用一個例子來總結一下一條查詢語言是如何實現的
select * from t where id = 123 and name = 'tom' 復制代碼
1 客戶端與MySQL服務端建立網絡連接,連接語句譬如:
mysql -h 127.0.0.1 -P 3306 -u 'name' -p'password!' database_name -A --default-character-set=utf8 復制代碼
這條語句指定了MySQL服務器的地址為 127.0.0.1 ,也就是本機,端口號為3306,用戶名為name,密碼為password。指定庫名為database_name,指定默認字符集為utf8。
2 完成連接后,如果開啟了MySQL的緩存機制,這時候會先去查詢緩存是否命中,如果緩存命中則直接返回緩存中的數據,如果緩存沒有命中則繼續向下執行。
3 分析器會分析每個詞是否是有意義的,比如會解析到 select 是SQL的關鍵詞, t 是表名, id 和 name 是表名中的字段.然后分析SQL的語法是否正常,該條語句可以正常執行。
4 優化器會分析在字段 id 和 name 上是否有索引,應該選擇哪個索引。如果表 t 是以 id 為主鍵,那么分析器就會直接走主鍵索引了。
5 執行器開始執行前會先校驗該用戶是否有對該的讀權限。通過權限校驗后,執行器會調用存儲引擎的API查詢出這條數據,返回給客戶端。
更新語句如何實現
一條更新語句的執行也要經歷一條查詢語句所要經歷的幾個階段,連接器建立連接、分析器分析語法、優化器選擇索引,執行器調用存儲引擎的API,與查詢語句相比,更新語句更為復雜,因為MySQL的InnoDB引擎要保證在數據庫機器宕機以后數據不丟失。
同樣以一個例子來總結查詢語句是如何實現的
update t set name = 'tom' where id = 123 復制代碼
1 客戶端與MySQL服務端建立網絡連接
2 分析器解析出這是一條更新語句
3 優化器選擇主鍵索引,假設以 id 做該表的主鍵
4 執行器首先查詢內存中是否有表 t 中 id 等于123的這一行數據,如果沒有則通過存儲引擎將這行數據取到內存中
5 執行器修改 name 字段為tom,得到一個新的行
6 存儲引擎將新行的數據寫入內存,并寫redo log日志, 此時 redo log 處于 prepare 狀態
7 執行器寫bin log日志
8 存儲引擎修改redo log日志為commit狀態
以上步驟就是一個完整的更新語句執行過程,細心的讀者會發現更新的數據只寫入到內存,還沒有持久化到磁盤,mysql異步定期將內存中的數據寫入到磁盤,這一過程和操作系統的文件系統讀寫很像,文件系統中有一個page cache,寫文件時先寫cache然后用一個獨立的進程將數據刷到磁盤。mysql使用了redo log日志,因此即使服務器宕機,數據也不會丟失,可以從redo log日志中恢復。
redo log日志與bin log日志
1 redo log日志是由server層來寫,bin log日志由存儲引擎來寫的;
2 redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改",bin log用于記錄邏輯操作。在statement模式時,bin log記的就是SQL語句;
3 redo log日志循環寫的,空間用完后,要先將數據刷到磁盤,然后清理空間。bin log日志是追加寫入的;
4 redo log日志用于數據庫崩潰后恢復數據,而bin log日志則用于主備同步,數據備份等;