一、MySQL基本架構
從該圖可以看出,MySQL 主要分為 Server 層和存儲引擎層:
- Server 層中包含連接器,查詢緩存,分析器,優化器,執行器,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能(存儲過程、觸發器、視圖等)都在這一層實現。
- 存儲引擎層主要負責最終數據的存儲和提取,其架構模式是插件式的,支持 InnoDB、MyISAM、Memory等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎。
二、SELECT 語句的執行過程
SELECT 語句的執行過程為:連接、查詢緩存、詞法分析,語法分析,語義分析,構造執行樹,生成執行計划、執行器執行計划,下面開始梳理一次完整的查詢流程:
連接器
- 連接器負責與客戶端建立連接,獲取權限,維持和管理連接
- 連接命令: >mysql -uroot -p123456 -h127.0.0.1 -P3306 -A
- 其中 mysql 是自帶的一個客戶端連接工具
- 連接的基本流程: 認證用戶名+密碼 -> 權限列表中查詢擁有的權限
- 后續的權限判斷,都依賴於此時讀到的權限.因此在修改權限之后要想生效需要重新登錄
- 連接建立后,無其他動作,則此連接將處於空閑狀態.
- 查看命令: >show processlist
- Command列若顯示為Sleep,則表示空閑
- 若連接后無任何動作,連接器會自動斷開
- 控制參數: wait_timeout, 默認8小時
- 針對非交互式連接
- 例: jdbc的方式
- 控制參數: interactive_timeout, 默認8小時
- 針對交互式連接(交互式連接:即在mysql_real_connect()函數中使用了CLIENT_INTERACTIVE選項)
- 例:終端的連接方式
- 資料:
- 長連接&短連接
- 建議使用長連接,減少資源建立時的開銷,因為建立連接的過程比較復雜(開空間,驗證密碼,查權限...)
- 但是如果都使用長連接,則可能會導致MySQL占用內存增長過快而出現內存溢出
- 原因是MySQL在執行過程中臨時使用的內存是管理在連接對象里面的.這些資源是要到連接斷開時才會釋放的.
- 長期積累會導致內存溢出,被系統強行kill掉,現象就是MySQL異常重啟
- 解決:
- 定期斷開長連接.使用一段時間/程序判斷執行過一個占用內存極大的查詢過后斷開連接,之后在重連
- MySQL5.7之后,可在每次執行一個較大的操作后,執行mysql_reset_connection來重新初始化連接資源.此過程不需要重連和重新做權限驗證.會將連接恢復到剛剛創建完成時的狀態.
- mysql_reset_connection參考資料:
查詢緩存
- 執行過的查詢會被 MySQL 以 key-value 的形式緩存起來. key 是查詢語句, value 是查詢結果
- 實際使用時不建議開啟此功能.原因是 MySQL 在執行 update 時會將整個表的所有緩存都失效
- 定制化配置:
- query_cache_type=DEMAND, 默認的 SQL 都不使用查詢緩存.
- 顯示指定使用緩存:
- select SQL_CACHE * from tb_xxx where id=xxx;
- MySQL8.0以上版本已將此功能廢棄
分析器
- 解析SQL語句
- 詞法分析: 解析輸入的語句的每個單詞,將 select 識別為查詢語句,from 之后的字符串識別為表等
- 從 information schema 里面獲得表的結構信息
- 語法分析: 基於詞法分析的結果,語法分析器會判斷是否滿足 MySQL 語法規則
優化器
- 經過分析器之后, MySQL 即知道具體需要做什么操作,但是在具體操作之前要先經過優化器
- 優化:
- 表中若存在多個索引時,選擇該使用哪個索引
- 多表關聯時,決定各表的連接順序
執行器
- 具體該執行的操作.
- 執行之前要先判斷對表的操作是否具備權限.如果沒有會返回權限錯誤的提示
- 如果存在查詢緩存,會在查詢緩存返回結果時來做權限驗證,查詢會在優化器之前調用 precheck 驗證權限
- 具備權限之后,即打開表開始執行.打開表時會根據表的引擎定義來選擇具體的引擎,並調用其接口來執行
- 執行過程(無索引,InnoDB):
- 調用 InnoDB 引擎取此表的第一行數據,判斷 Where 條件是否滿足,滿足則將此行存在結果集中, 不滿足則跳過
- 調用 InnoDB 引擎取下一行數據,重復上述邏輯,直到最后一行
- 執行器將所有滿足條件的行作為結果集返回給客戶端
- 問題:
- 對表的權限驗證為何是在執行器階段來執行?
- SQL語句要操作的表不只是SQL字面上那些,例如觸發器,得在執行器階段(過程中)才能確定。優化器等其他階段是無能為力的
- 對表的權限驗證為何是在執行器階段來執行?
存儲引擎
- 負責數據的存儲和提取
總結
- 1、客戶端與服務端連接,連接器來負責建立連接
- cmd: mysql -uroot -p123456 -h127.0.0.1 -P3306 -A
- 過程:
- 驗證用戶名+密碼
- 從權限列表中查詢所擁有的權限
- 2、判斷是否命中查詢緩存,檢查當前是否開啟了查詢緩存(query_cache_type)
- 如果開啟了查詢緩存,則用當前 sql 作為 key 去緩存中查詢,如果存在,則直接返回結果
- 3、分析SQL: 分析器工作,分析SQL,先做詞法分析
- 識別出關鍵字如 select,insert, from 后的表, where 后的查詢條件等
-
4、語法分析,基於詞法分析的結果,來識別當前的SQL是否滿足語法規定,比如關鍵字的使用先后順序等
- 5、優化SQL執行,優化器工作,優化SQL的執行
- 如:選擇要使用的索引
- 如:連表時選擇的連接順序
- 6、執行SQL,執行器工作,執行SQL語句
- 判斷對此表是否具有查詢權限
- 權限具備,則打開表開始執行
- 根據表的引擎定義,選擇具體的引擎,去調引擎的接口執行查詢
- 查詢到的數據放入內存中,放入結果集里.
- 查詢完畢后,將結果集返回給客戶端
三、UPDATE 語句執行過程
UPDATE 語句執行過程總體上和 SELECT 語句是差不多的,分為:連接、查詢緩存、詞法分析,語法分析,語義分析,構造執行樹,生成執行計划、執行器執行計划。但是有兩個過程是完全不一樣的:
- 第一個是查詢緩存階段,SELECT 語句是去緩存查有沒有相同 SELECT 語句,並將其結果取出返回給客戶端,而 UPDATE 語句是去清空該表的查詢緩存。
- 第二個是執行器階段,SELECT 語句是將磁盤上的數據取出,而 UPDATE 語句是先查到這些數據,然后進行更新並寫入磁盤。
- 其他包括連接、詞法分析、語法分析、生成執行計划等過程都是一樣的。
更新流程涉及到兩個重要的日志模塊,binlog(歸檔日志) 和 redo log(重做日志)
binlog
binlog 記錄了對 MySQL 數據庫執行更改的所有操作,但是不包括 SELECT 和 SHOW 這類操作,因為這類操作對數據本身並沒有修改。若操作本身並沒有導致數據庫發生變化,那么該操作也會寫入二進制日志。MySQL 的主從賦值就是依靠 binlog。
redo log
redo log又稱重做日志文件,用於記錄事務操作的變化,記錄的是數據修改之后的值,不管事務是否提交都會記錄下來。在實例和介質失敗(media failure)時,redo log文件就能派上用場,如數據庫掉電,InnoDB存儲引擎會使用 redo log 恢復到掉電前的時刻,以此來保證數據的完整性。
binlog 和 redo log 的區別
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。
- redo log 是物理日志,記錄的是 “在某個數據頁上做了什么修改”。binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
- redo log 是循環寫的,空間固定會用完。binlog 是可以追加寫入的,“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。
通過以上對 binlog 和 redo log 的描述,我們再來看看 UPDATE 語句的執行過程:
- 例如
- mysql> update T set c=100 where ID=2;
- 流程
- 執行器先調用引擎接口取 ID=2 這一行
- 如果 ID 是主鍵,引擎直接用樹搜索找到這一行。
- 如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器
- 否則,需要先從磁盤讀入內存,然后再返回。
- 執行器拿到引擎給的行數據
- 把這個值改為 100,得到新的一行數據,再調用引擎接口寫入這行新數據。
- 引擎將這行新數據更新到內存中
- 同時將這個更新操作記錄到 redo log 里面,此時 redo log 處於 prepare 狀態。
- 然后告知執行器執行完成了,隨時可以提交事務。
- 執行器生成這個操作的 binlog,並把 binlog 寫入磁盤。
- 執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。
- 執行器先調用引擎接口取 ID=2 這一行
redo 兩階段提交
根據上面的介紹,可以發現 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。binlog
是 MySQL 內部實現二階段提交的協調者,它為每個事務分配一個事務 ID。
- 原因
- 由於 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完 redo log 再寫 binlog,或者采用反過來的順序,會導致數據的不一致出現。
- 簡單說,redo log 和 binlog 都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。
- 一階段
- 開啟事務,redo log 和 undo log 已經記錄了對應的日志,此時事務狀態為
prepare
- 開啟事務,redo log 和 undo log 已經記錄了對應的日志,此時事務狀態為
- 二階段
binlog
完成write 和 fsync
后,成功,事務一定提交了,否則事務回滾 發送 commit,清除 undo 信息,刷 redo,設置事務狀態為 completed。
- 舉例
- 賬本記上 賣一瓶可樂(redo log為 prepare狀態),然后收錢放入錢箱(bin log記錄)然后回過頭在賬本上打個勾(redo log置為commit)表示一筆交易結束。
- 如果收錢時交易被打斷,回過頭來整理此次交易,發現只有記賬沒有收錢,則交易失敗,刪掉賬本上的記錄(回滾)。
- 如果收了錢后被終止,然后回過頭發現賬本有記錄(prepare)而且錢箱有本次收入(bin log),則繼續完善賬本(commit),本次交易有效。