MySQL==> SQL執行流程分析


一、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)狀態,更新完成。

redo 兩階段提交

根據上面的介紹,可以發現 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。binlog 是 MySQL 內部實現二階段提交的協調者,它為每個事務分配一個事務 ID。

  • 原因
    • 由於 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完 redo log 再寫 binlog,或者采用反過來的順序,會導致數據的不一致出現。
    • 簡單說,redo log 和 binlog 都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。
  • 一階段
    • 開啟事務,redo log 和 undo log 已經記錄了對應的日志,此時事務狀態為 prepare
  • 二階段
    • binlog 完成 write 和 fsync 后,成功,事務一定提交了,否則事務回滾 發送 commit,清除 undo 信息,刷 redo,設置事務狀態為 completed。
  • 舉例
    • 賬本記上 賣一瓶可樂(redo log為 prepare狀態),然后收錢放入錢箱(bin log記錄)然后回過頭在賬本上打個勾(redo log置為commit)表示一筆交易結束。
    • 如果收錢時交易被打斷,回過頭來整理此次交易,發現只有記賬沒有收錢,則交易失敗,刪掉賬本上的記錄(回滾)。
    • 如果收了錢后被終止,然后回過頭發現賬本有記錄(prepare)而且錢箱有本次收入(bin log),則繼續完善賬本(commit),本次交易有效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM