歡迎光臨
每天分享高質量文章

我是一條DQL

來自:孤獨煙(微訊號:zrj_guduyan)

 

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

本文改編自《高效能Mysql》,煙哥用小說的形式來講這個內容。

序章 自我介紹

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

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


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

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

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

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

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

SHOW [FULLPROCESSLIST

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

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

你看圖裡,有一個Sleep,這是在告訴你執行緒正在等待客戶端傳送新的請求。還有一個為Query,這代表執行緒正在執行查詢或者正在將結果傳送給客戶端。
至於其他的,還有LockedSending data等等,分別代表…

額,好吧,嘮嘮叨叨了一大堆,大家居然木有嫌我煩,嗯,至於其他狀態的含義大家可以去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任何字元上的不同,如空格,註釋,都會導致快取不命中

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

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

(本文將解析器和前處理器統一稱為分析器)
話說,我離開查詢快取後,進入解析器。
解析器:”來來來,我先對你進行詞法分析,告訴我你長啥樣?
我是下麵這樣的

select username from userinfo

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

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

我:“如果語法不對呢?
解析器:”那你會收到一個提示如下!

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頁有一句話如下圖所示

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

論點三:同原始碼不符

我翻看了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這個方法中。

當然,大家有新的見解,歡迎留言。

    已同步到看一看
    贊(0)

    分享創造快樂