面試官:說說一條查詢sql的執行流程和底層原理?

一條查詢SQL執行流程圖如下

面試官:說說一條查詢sql的執行流程和底層原理?

序章 自我介紹

我是一條sql,就是一條長長的字符串,不要問我長什麼樣,因為我比較傲嬌。

面試官:說說一條查詢sql的執行流程和底層原理?

額~~不是我不說啊,因為細說起來,我可以細分為DML(Update、Insert、Delete),DDL(表結構修改),DCL(權限操作),DQL(Select)操作,一個個去介紹,我怕大家嫌我煩!

嗯,大家沒什麼意見,我繼續往下自我介紹了~

由於種類太多,這裡我只是一條查詢SQL,也就是一句DQL。

客戶端按照Mysql通信協議,把我發送到服務端。

當我到達服務端後,我會在一個單獨的線程裡進行執行。服務端要先…

面試官:說說一條查詢sql的執行流程和底層原理?

萬萬沒想到,我又被打斷了~好吧,因為我在一個線程裡執行,總要有辦法能看到線程的執行狀態吧。Mysql提供了下面的命令,給大家查看

SHOW [FULL] PROCESSLIST


出來的結果是長下面這樣的

面試官:說說一條查詢sql的執行流程和底層原理?

圖裡Command這一列,反應的就是這個線程當前的執行狀態啦。我在這個線程的執行過程中,狀態是會變化很多次。

你看圖裡,有一個Sleep,這是在告訴你線程正在等待客戶端發送新的請求。還有一個為Query,這代表線程正在執行查詢或者正在將結果發送給客戶端。

至於其他的,還有Locked、Sending data等等,分別代表…

面試官:說說一條查詢sql的執行流程和底層原理?

額,好吧,嘮嘮叨叨了一大堆,大家居然木有嫌我煩,嗯,至於其他狀態的含義大家可以去Mysql官網查詢哦。

嗯,回到剛才的話題。我到達服務端後,Mysql要判斷我的前6個字符是否為select。並且,語句中不帶有SQL_NO_CACHE關鍵字,如果符合條件,就進入查詢緩存。

第一章 我和查詢緩存的那些事

說到查詢緩存,它其實是一個哈希表,它將執行過的語句及其結果會以 key-value 對的形式,被直接緩存在內存中。

它的key是一個哈希值,是通過查詢SQL(也就是我)、當前要查詢的數據庫、客戶端協議版本等,生成的一個哈希值,而它的value自然就是查詢結果啦。

當然,如果我要繞過查詢緩存,也很簡單。我可以像下面這麼寫:

Select SQL_NO_CACHE * from table


也可以將參數query_cache_type設置成DEMAND來繞過查詢緩存。

可是,有一天查詢緩存悲傷的對我說:"你將來再也看不到我了,我已經被歷史淘汰了,Mysql8.0版本開始就沒有我了!"

聽到這個消息後,我表面上故作堅強的對查詢緩存說:"不要方,大家會想你的!"

然而,實際上心裡想的是:"嘿嘿嘿,你個坑爹的,終於不存在了!"大家不要覺得我太邪惡,畢竟查詢緩存實在是太不好用了。接下來我們來說說解析器…


面試官:說說一條查詢sql的執行流程和底層原理?

萬萬沒想到,本來想糊弄過去的。結果…好吧,回到正題,因為

  • 只要有對一個表的更新,這個表上所有的查詢緩存都會被清空
  • SQL任何字符上的不同,如空格,註釋,都會導致緩存不命中

因此,我能想到用查詢緩存的表,只有一種情況,那就是配置表。其他的業務表,根本是無法利用查詢緩存的特性,或許Mysql團隊也是覺得查詢緩存的使用場景過於侷限,就無情的將它剔除。

第二章 我和分析器的愛恨情仇

(本文將解析器和預處理器統一稱為分析器)

話說,我離開查詢緩存後,進入解析器。

解析器:"來來來,我先對你進行詞法分析,告訴我你長啥樣?"

我是下面這樣的

select username from userinfo


解析器:"好,好,好。我有兩個階段,我先對你進行詞法分析,我將你從左到右一個字符、一個字符地輸入,然後根據構詞規則識別單詞。你將會生成4個Token,如下所示。"

面試官:說說一條查詢sql的執行流程和底層原理?

解析器:"接下來呢,進行語法解析,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法。然後生成下面這樣一顆語法樹。"

面試官:說說一條查詢sql的執行流程和底層原理?


我:"如果語法不對呢?"

解析器:"那你會收到一個提示如下!"

You have an error in your SQL syntax


解析器:"順利生成語法樹以後,我就將你送往預處理器!"

預處理器:"老弟,你來拉!"

我:"嗯!"

預處理器:"老弟,我來幫你看看你的列名對不對,數據庫的這張表裡是不是真的有這個列。再看看錶名對不對,如果不對,你會看到下面的錯誤!"

Unknown column xxx in ‘where clause’


預處理器:"最後我再給你送去做權限驗證,如果你沒有操作這個表的權限,會報下面這個錯誤!"

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


(這個地方,大家可能有疑問,因為有些文章說是執行器做的權限驗證,可以直接拉到本文底部看說明)

最後,這顆語法樹會傳遞給優化器。

第三章 我和優化器的動人過往

在告別瞭解析器後,我進入了優化器。

優化器大哥:"告訴我,你長什麼樣啊?"

我說道:"大哥不要捉急,我是長這樣的~"(這裡優化的其實應該是語法樹,我只是為了便於說明,才用SQL當例子,實際上是針對語法樹進行優化)

select t1.*
from Table1 t1
inner join Table2 t2
on t1.CommonID = t2.CommonID

優化器大哥:"我的任務就是幫你判斷一下怎麼樣執行更快,比如先查Table1再查Table2,還是先查Table2再查Table1呢?判斷完如何執行以後,生成執行計劃就好啦!"

我很不信任的說道:“哼,你就不會判斷失誤麼!”

優化器大哥:"那就要對SQL進行改寫啦,比如你帶了STRAIGHT_JOIN關鍵字,長下面這樣"

select t1.*
from Table1 t1
STRAIGHT_JOIN Table2 t2
on t1.CommonID = t2.CommonID

"那我就知道強制先找Table1再關聯找Table2啦,類似的例子還有很多,我就不一一列舉了!"

(STRAIGHT_JOIN功能同join類似,但能讓左邊的表來驅動右邊的表,能改表優化器對於聯表查詢的執行順序。)

我說道:"哇塞,如何編寫一個高效的SQL,真是一門學問啊!"

於是,優化器大哥將我變身為一個執行計劃,然後交給執行器啦~

第四章 我和執行器的悲情經歷

我:"執行器大哥,你是用來做什麼的?"

執行器:"就是根據執行計劃來進行執行查詢啦。我就根據你的指令,逐條調用底層存儲引擎,逐步執行。"

MySQL定義了一系列抽象存儲引擎API,以支持插件式存儲引擎架構。Mysql實現了一個抽象接口層,叫做 handler(sql/handler.h),其中定義了接口函數,比如:ha_open, ha_index_end, ha_create等等,存儲引擎需要實現這些接口才能被系統使用。

末章 一些感慨

最後一個階段,Mysql會將查詢結果返回客戶端。

唯一需要說明的是,如果是SELECT類型的SQL,Mysql會將查詢結果緩存起來。至於其他的SQL,就將該表涉及到的查詢緩存清空。

一些疑問

這裡關於權限驗證究竟在哪個階段執行,大家可能會有一些疑問。

之前有一個大牛的文章說是權限驗證是在執行階段,去執行前驗證權限,大家如果看過他的文章,可能會有疑問。我也不是亂質疑人家,畢竟我只是一個小咖。我在這裡只是發表一下我自己的論點,歡迎大家拍磚。

論點一:權限驗證在執行器中判斷從邏輯上說不通

一條查詢SQL經過查詢緩存、分析器、優化器,執行器。如果到最後一個階段執行器中才發現權限不足、那不是前面一系列流程白做了,Mysql應該不至於這麼傻吧~

論點二:同《高性能Mysql》一書內容不符

該書209頁有一句話如下圖所示

面試官:說說一條查詢sql的執行流程和底層原理?

該書也指明權限驗證是在預處理器中執行。本文中將預處理和解析器統一劃分為分析器的範疇。

論點三:同源碼不符

我翻看了Mysql5.7.25這個版本的源碼,其在處理查詢這段的核心代碼如下

在sql_parse.cc文件中,有這麼一段代碼如下

case SQLCOM_SELECT:
{
//省略
res= select_precheck(thd, lex, all_tables, first_table);
if (!res)
res= execute_sqlcom_select(thd, all_tables);
//省略
}

其中select_precheck是進行權限校驗。而優化器和執行器是在execute_sqlcom_select這個方法中。


分享到:


相關文章: