想要深刻的瞭解MySQL,首先要了解MySQL語句是怎麼實現的,瞭解了MySQL里語句的執行過程能夠更加快速的分析問題的緣由,或者進行合理的優化。mysql
MySQL的架構圖以下所示,主要由如下幾個部分組成:鏈接器,緩存,分析器,優化器,執行器和存儲引擎。sql
MySQL能夠分爲server層和存儲引擎層,server層包括鏈接器、分析器、優化器和執行器,主要負責SQL語法的解析,內置函數的實現,觸發器,視圖等。存儲引擎層負責數據的存儲和提取,存儲引擎是插件式的,MySQL支持的存儲引擎就有InnoDB、MyISAM、Memory等。目前,InnoDB是mysql默認的存儲引擎。數據庫
鏈接器負責與客戶端創建網絡鏈接、校驗用戶名密碼、校驗用戶權限、維持和管理鏈接等。編程
網絡鏈接創建後,首先驗證用戶名和密碼,用戶名和密碼驗證經過之後鏈接器會到權限表裏查詢該用戶的權限。以後,這個鏈接裏的權限判斷邏輯,都將依賴於此時讀到的權限。這就意味着,一個用戶成功鏈接後,再去修改該用戶的權限,也不會影響到已經創建好的鏈接,只有從新創建鏈接權限纔會生效。緩存
MySQL的網絡鏈接採用的是多線程模型,維護一個線程池,每當有一個新的鏈接請求時,就從空閒的線程池中選擇一個線程進行處理。可使用show processlist
命令看到當前所創建的全部鏈接。bash
+------------+--------------+--------------------+------------------+---------+-------+-------+------------------+
| 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日誌中恢復。
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日誌則用於主備同步,數據備份等;