作者:孤獨煙太援,資深后端工程師,業(yè)內(nèi)知名原創(chuàng)作者
一條查詢SQL執(zhí)行流程圖如下
本文改編自《高性能Mysql》扳碍,煙哥用小說的形式來講這個內(nèi)容提岔。
序章 自我介紹
我是一條sql,就是一條長長的字符串笋敞,不要問我長什么樣碱蒙,因為我比較傲嬌。
額~~不是我不說啊夯巷,因為細(xì)說起來赛惩,我可以細(xì)分為
DML
(Update、Insert趁餐、Delete),
DDL
(表結(jié)構(gòu)修改),
DCL
(權(quán)限操作),
DQL
(Select)操作,一個個去介紹喷兼,我怕大家嫌我煩!
嗯后雷,大家沒什么意見季惯,我繼續(xù)往下自我介紹了~
由于種類太多,這里我只是一條查詢SQL臀突,也就是一句DQL勉抓。
客戶端按照Mysql通信協(xié)議,把我發(fā)送到服務(wù)端候学。
當(dāng)我到達(dá)服務(wù)端后藕筋,我會在一個單獨的
線程
里進(jìn)行執(zhí)行。服務(wù)端要先…
萬萬沒想到盒齿,我又被打斷了~好吧念逞,因為我在一個線程里執(zhí)行困食,總要有辦法能看到線程的執(zhí)行狀態(tài)吧。Mysql提供了下面的命令翎承,給大家查看
SHOW [FULL] PROCESSLIST
出來的結(jié)果是長下面這樣的
圖里
Command
這一列硕盹,反應(yīng)的就是這個線程當(dāng)前的執(zhí)行狀態(tài)啦。我在這個線程的執(zhí)行過程中叨咖,狀態(tài)是會變化很多次瘩例。
你看圖里,有一個
Sleep
甸各,這是在告訴你線程正在等待客戶端發(fā)送新的請求垛贤。還有一個為
Query
,這代表線程正在執(zhí)行查詢或者正在將結(jié)果發(fā)送給客戶端。
至于其他的趣倾,還有
Locked
Sending data
等等聘惦,分別代表…
額,好吧儒恋,嘮嘮叨叨了一大堆善绎,大家居然木有嫌我煩,嗯诫尽,至于其他狀態(tài)的含義大家可以去Mysql官網(wǎng)查詢哦禀酱。
嗯,回到剛才的話題牧嫉。我到達(dá)服務(wù)端后剂跟,Mysql要判斷我的前6個字符是否為
select
。并且酣藻,語句中不帶有
SQL_NO_CACHE
關(guān)鍵字曹洽,如果符合條件,就進(jìn)入查詢緩存臊恋。
第一章 我和查詢緩存的那些事
說到查詢緩存衣洁,它其實是一個哈希表,它將執(zhí)行過的語句及其結(jié)果會以 key-value 對的形式抖仅,被直接緩存在內(nèi)存中坊夫。
它的key是一個哈希值,是通過查詢SQL(也就是我)撤卢、當(dāng)前要查詢的數(shù)據(jù)庫环凿、客戶端協(xié)議版本等,生成的一個哈希值放吩,而它的value自然就是查詢結(jié)果啦智听。
當(dāng)然,如果我要繞過查詢緩存,也很簡單到推。我可以像下面這么寫:
Select SQL_NO_CACHE * from table
也可以將參數(shù)query_cache_type
設(shè)置成DEMAND
來繞過查詢緩存考赛。
可是,有一天查詢緩存悲傷的對我說:"你將來再也看不到我了莉测,我已經(jīng)被歷史淘汰了颜骤,Mysql8.0版本開始就沒有我了!"
聽到這個消息后捣卤,我表面上故作堅強(qiáng)的對查詢緩存說:"不要方忍抽,大家會想你的!"
然而董朝,實際上心里想的是:"嘿嘿嘿鸠项,你個坑爹的,終于不存在了子姜!"大家不要覺得我太邪惡祟绊,畢竟查詢緩存實在是太不好用了。接下來我們來說說解析器…
萬萬沒想到闲询,本來想糊弄過去的久免。結(jié)果…好吧,回到正題扭弧,因為
- 只要有對一個表的更新,這個表上所有的查詢緩存都會被清空
- SQL任何字符上的不同,如空格,注釋,都會導(dǎo)致緩存不命中
因此记舆,我能想到用查詢緩存的表鸽捻,只有一種情況,那就是配置表泽腮。其他的業(yè)務(wù)表御蒲,根本是無法利用查詢緩存的特性,或許Mysql團(tuán)隊也是覺得查詢緩存的使用場景過于局限诊赊,就無情的將它剔除厚满。
第二章 我和分析器的愛恨情仇
(本文將解析器和預(yù)處理器統(tǒng)一稱為分析器)
話說,我離開查詢緩存后碧磅,進(jìn)入解析器碘箍。
解析器:"來來來,我先對你進(jìn)行詞法分析鲸郊,告訴我你長啥樣丰榴?"
我是下面這樣的
select username from userinfo
解析器:"好,好秆撮,好四濒。我有兩個階段,我先對你進(jìn)行詞法分析,我將你從左到右一個字符盗蟆、一個字符地輸入戈二,然后根據(jù)構(gòu)詞規(guī)則識別單詞。你將會生成4個Token,如下所示喳资。"
解析器:"接下來呢觉吭,進(jìn)行語法解析,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法骨饿。然后生成下面這樣一顆語法樹亏栈。"
我:"如果語法不對呢?"
解析器:"那你會收到一個提示如下宏赘!"
You have an error in your SQL syntax
解析器:"順利生成語法樹以后绒北,我就將你送往預(yù)處理器!"
預(yù)處理器:"老弟察署,你來拉!"
我:"嗯闷游!"
預(yù)處理器:"老弟,我來幫你看看你的列名對不對贴汪,數(shù)據(jù)庫的這張表里是不是真的有這個列脐往。再看看表名對不對,如果不對扳埂,你會看到下面的錯誤业簿!"
Unknown column xxx in ‘where clause’
預(yù)處理器:"最后我再給你送去做權(quán)限驗證,如果你沒有操作這個表的權(quán)限阳懂,會報下面這個錯誤!"
ERROR 1142 (42000): SELECT command denied to user 'root'@'localhost' for table 'xxx'
(這個地方梅尤,大家可能有疑問,因為有些文章說是執(zhí)行器做的權(quán)限驗證岩调,可以直接拉到本文底部看說明)
最后巷燥,這顆語法樹會傳遞給優(yōu)化器。
第三章 我和優(yōu)化器的動人過往
在告別了解析器后号枕,我進(jìn)入了優(yōu)化器缰揪。
優(yōu)化器大哥:"告訴我,你長什么樣按写尽钝腺?"
我說道:"大哥不要捉急,我是長這樣的~"(這里優(yōu)化的其實應(yīng)該是語法樹蛙紫,我只是為了便于說明拍屑,才用SQL當(dāng)例子,實際上是針對語法樹進(jìn)行優(yōu)化)
select t1.*
from Table1 t1
inner join Table2 t2
on t1.CommonID = t2.CommonID
優(yōu)化器大哥:"我的任務(wù)就是幫你判斷一下怎么樣執(zhí)行更快坑傅,比如先查Table1
再查Table2
僵驰,還是先查Table2
再查Table1
呢?判斷完如何執(zhí)行以后,生成執(zhí)行計劃就好啦蒜茴!"
我很不信任的說道:“哼星爪,你就不會判斷失誤么!”
優(yōu)化器大哥:"那就要對SQL進(jìn)行改寫啦粉私,比如你帶了STRAIGHT_JOIN
關(guān)鍵字顽腾,長下面這樣"
select t1.*
from Table1 t1
STRAIGHT_JOIN Table2 t2
on t1.CommonID = t2.CommonID
"那我就知道強(qiáng)制先找Table1
再關(guān)聯(lián)找Table2
啦,類似的例子還有很多诺核,我就不一一列舉了抄肖!"
(STRAIGHT_JOIN
功能同join類似,但能讓左邊的表來驅(qū)動右邊的表窖杀,能改表優(yōu)化器對于聯(lián)表查詢的執(zhí)行順序漓摩。)
我說道:"哇塞,如何編寫一個高效的SQL入客,真是一門學(xué)問肮鼙小!"
于是桌硫,優(yōu)化器大哥將我變身為一個執(zhí)行計劃夭咬,然后交給執(zhí)行器啦~
第四章 我和執(zhí)行器的悲情經(jīng)歷
我:"執(zhí)行器大哥,你是用來做什么的铆隘?"
執(zhí)行器:"就是根據(jù)執(zhí)行計劃來進(jìn)行執(zhí)行查詢啦卓舵。我就根據(jù)你的指令,逐條調(diào)用底層存儲引擎膀钠,逐步執(zhí)行边器。"
MySQL
定義了一系列抽象存儲引擎API,以支持插件式存儲引擎架構(gòu)托修。Mysql實現(xiàn)了一個抽象接口層,叫做 handler(sql/handler.h)
恒界,其中定義了接口函數(shù)睦刃,比如:ha_open
, ha_index_end
, ha_create
等等,存儲引擎需要實現(xiàn)這些接口才能被系統(tǒng)使用十酣。
末章 一些感慨
最后一個階段涩拙,Mysql會將查詢結(jié)果返回客戶端。
唯一需要說明的是耸采,如果是SELECT類型的SQL兴泥,Mysql會將查詢結(jié)果緩存起來。至于其他的SQL虾宇,就將該表涉及到的查詢緩存清空搓彻。
一些疑問
這里關(guān)于權(quán)限驗證究竟在哪個階段執(zhí)行,大家可能會有一些疑問。
之前有一個大牛的文章說是權(quán)限驗證是在執(zhí)行階段旭贬,去執(zhí)行前驗證權(quán)限怔接,大家如果看過他的文章,可能會有疑問稀轨。我也不是亂質(zhì)疑人家扼脐,畢竟我只是一個小咖。我在這里只是發(fā)表一下我自己的論點奋刽,歡迎大家拍磚瓦侮。
論點一:權(quán)限驗證在執(zhí)行器中判斷從邏輯上說不通
一條查詢SQL經(jīng)過查詢緩存、分析器佣谐、優(yōu)化器肚吏,執(zhí)行器。如果到最后一個階段執(zhí)行器中才發(fā)現(xiàn)權(quán)限不足台谍、那不是前面一系列流程白做了须喂,Mysql應(yīng)該不至于這么傻吧~
論點二:同《高性能Mysql》一書內(nèi)容不符
該書209頁有一句話如下圖所示
該書也指明權(quán)限驗證是在預(yù)處理器中執(zhí)行。本文中將預(yù)處理和解析器統(tǒng)一劃分為分析器的范疇趁蕊。
論點三:同源碼不符
我翻看了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
是進(jìn)行權(quán)限校驗掷伙。而優(yōu)化器和執(zhí)行器是在execute_sqlcom_select
這個方法中是己。
當(dāng)然,大家有新的見解任柜,歡迎留言卒废。