一條SQL語句,在MySQL中是如何執行的


mysql> select * from T where ID=10

我們看到的只是輸入一條語句,返回一個結果,卻不知道這條語句在 MySQL 內部的執行過程。

MySQL 的基本架構示意圖,從中你可以清楚地看到 SQL 語句在 MySQL 的各個功能模塊中的執行過程。

(圖源https://blog.csdn.net/Megustas_JJC/article/details/84380108

大體來說,MySQL 可以分為 Server 層和存儲引擎層兩部分。

  • Server 層包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等。
  • 而存儲引擎層負責數據的存儲和提取。其架構模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎。

也就是說,你執行 create table 建表的時候,如果不指定引擎類型,默認使用的就是 InnoDB。不過,你也可以通過指定存儲引擎的類型來選擇別的引擎,比如在 create table 語句中使用 engine=memory, 來指定使用內存引擎創建表。

查詢緩存

由於表經常更新,查詢緩存的失效頻繁,查詢緩存往往利大於弊。,MySQL 8.0 版本開始直接將查詢緩存的整塊功能刪掉了。

優化器

經過了分析器,MySQL 就知道你要做什么了。在開始執行之前,還要先經過優化器的處理。

優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序。比如你執行下面這樣的語句,這個語句是執行兩個表的 join:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;
  • 既可以先從表 t1 里面取出 c=10 的記錄的 ID 值,再根據 ID 值關聯到表 t2,再判斷 t2 里面 d 的值是否等於 20。
  • 也可以先從表 t2 里面取出 d=20 的記錄的 ID 值,再根據 ID 值關聯到 t1,再判斷 t1 里面 c 的值是否等於 10。
    這兩種執行方法的邏輯結果是一樣的,但是執行的效率會有不同,而優化器的作用就是決定選擇使用哪一個方案。

如果你還有一些疑問,比如優化器是怎么選擇索引的,有沒有可能選擇錯等等,沒關系,我會在后面的文章中單獨展開說明優化器的內容。

執行器

開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤,如下所示:

mysql> select * from T where ID=10;

ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口

比如我們這個例子中的表 T 中,ID 字段沒有索引,那么執行器的執行流程是這樣的:

  1. 調用 InnoDB 引擎接口取這個表的第一行,判斷 ID 值是不是 10,如果不是則跳過,如果是則將這行存在結果集中;
  2. 調用引擎接口取“下一行”,重復相同的判斷邏輯,直到取到這個表的最后一行。
  3. 執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端。

至此,這個語句就執行完成了。

對於有索引的表,執行的邏輯也差不多。第一次調用的是“取滿足條件的第一行”這個接口,之后循環取“滿足條件的下一行”這個接口,這些接口都是引擎中已經定義好的。

  • 既可以先從表 t1 里面取出 c=10 的記錄的 ID 值,再根據 ID 值關聯到表 t2,再判斷 t2 里面 d 的值是否等於 20。
  • 也可以先從表 t2 里面取出 d=20 的記錄的 ID 值,再根據 ID 值關聯到 t1,再判斷 t1 里面 c 的值是否等於 10。
    這兩種執行方法的邏輯結果是一樣的,但是執行的效率會有不同,而優化器的作用就是決定選擇使用哪一個方案。

上面是一條查詢sql,再看一條SQL語句的執行過程,更新操作:

update tb_student A set A.age='19' where A.name='張三'; 

其實條語句也基本上會沿着上一個查詢的流程走,只不過執行更新的時候肯定要記錄日志啦,這就會引入日志模塊了,mysql 自帶的日志模塊式binlog(歸檔日志),所有的存儲引擎都可以使用,我們常用的InnoDB引擎還自帶了一個日志模塊redo log,我們就以InnoDB模式下來探討這個語句的執行流程。流程如下:

  •  先查詢到張三這一條數據,如果有緩存,也是會用到緩存。
  •  然后拿到查詢的語句,把 age 改為19,然后調用引擎API接口,寫入這一行數據,InnoDB引擎把數據保存在內存中,同時記錄redo log,此時redo log進入prepare狀態,然后告訴執行器,執行完成了,隨時可以提交。
  •  執行器收到通知后記錄binlog,然后調用引擎接口,提交redo log 為提交狀態。
  •  更新完成。

 

這里肯定有同學會問,為什么要用兩個日志模塊,用一個日志模塊不行嗎?這就是之前mysql的模式了,MyISAM引擎是沒有redo log的,那么我們知道它是不支持事務的,所以並不是說只用一個日志模塊不可以,只是InnoDB引擎就是通過redo log來支持事務的。那么,又會有同學問,我用兩個日志模塊,但是不要這么復雜行不行,為什么redo log 要引入prepare預提交狀態?這里我們用反證法來說明下為什么要這么做?

  •  先寫redo log 直接提交,然后寫 binlog,假設寫完redo log 后,機器掛了,binlog日志沒有被寫入,那么機器重啟后,這台機器會通過redo log恢復數據,但是這個時候bingog並沒有記錄該數據,后續進行機器備份的時候,就會丟失這一條數據,同時主從同步也會丟失這一條數據。
  •  先寫binlog,然后寫redo log,假設寫完了binlog,機器異常重啟了,由於沒有redo log,本機是無法恢復這一條記錄的,但是binlog又有記錄,那么和上面同樣的道理,就會產生數據不一致的情況。

如果采用redo log 兩階段提交的方式就不一樣了,寫完binglog后,然后再提交redo log就會防止出現上述的問題,從而保證了數據的一致性。那么問題來了,有沒有一個極端的情況呢?假設redo log 處於預提交狀態,binglog也已經寫完了,這個時候發生了異常重啟會怎么樣呢? 這個就要依賴於mysql的處理機制了,mysql的處理過程如下:

  •  判斷redo log 是否完整,如果判斷是完整的,就立即提交。
  •  如果redo log 只是預提交但不是commit狀態,這個時候就會去判斷binlog是否完整,如果完整就提交 redo log, 不完整就回滾事務。

這樣就解決了數據一致性的問題。

 

 (圖源https://database.51cto.com/art/201903/594091.htm

 

參考鏈接:

1. https://database.51cto.com/art/201903/594091.htm

2. https://blog.csdn.net/Megustas_JJC/article/details/84380108


免責聲明!

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



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