【原創】小說:我是一條DQL

SQL執行流程圖如下

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

序章 自我介紹

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

額~~不是我不說啊,因爲細說起來,我可以細分爲DML(Update、Insert、Delete),DDL(表結構修改),DCL(權限操作),DQL(Select)操作,一個個去介紹,我怕大家嫌我煩!
嗯,大家沒什麼意見,我繼續往下自我介紹了~
由於種類太多,這裏我只是一條查詢SQL,也就是一句DQL。
客戶端按照Mysql通信協議,把我發送到服務端。
當我到達服務端後,我會在一個單獨的線程裏進行執行。服務端要先...

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

SHOW [FULL] PROCESSLIST

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

圖裏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,如下所示。"

關鍵字 非關鍵字 關鍵字 非關鍵字
select username from userinfo

解析器:"接下來呢,進行語法解析,判斷你輸入的這個 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這個方法中。
當然,大家有新的見解,歡迎留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章