MySQL我們可能是使用得最多的數(shù)據(jù)庫(kù)了,但你知道你編寫的SQL語(yǔ)句是怎么樣執(zhí)行的嗎?這篇文章通俗易懂地告訴你! 一條查詢SQL執(zhí)行流程圖如下 本文改編自《高性能Mysql》,煙哥用小說(shuō)的形式來(lái)講這個(gè)內(nèi)容。序章 自我介紹我是一條sql,就是一條長(zhǎng)長(zhǎng)的字符串,不要問(wèn)我長(zhǎng)什么樣,因?yàn)槲冶容^傲嬌。 額~~不是我不說(shuō)啊,因?yàn)榧?xì)說(shuō)起來(lái),我可以細(xì)分為DML (Update、Insert、Delete),DDL (表結(jié)構(gòu)修改),DCL (權(quán)限操作),DQL (Select)操作,一個(gè)個(gè)去介紹,我怕大家嫌我煩! 嗯,大家沒(méi)什么意見(jiàn),我繼續(xù)往下自我介紹了~ 由于種類太多,這里我只是一條查詢SQL,也就是一句DQL。 客戶端按照Mysql通信協(xié)議,把我發(fā)送到服務(wù)端。 當(dāng)我到達(dá)服務(wù)端后,我會(huì)在一個(gè)單獨(dú)的 線程里進(jìn)行執(zhí)行。服務(wù)端要先…萬(wàn)萬(wàn)沒(méi)想到,我又被打斷了~好吧,因?yàn)槲以谝粋€(gè)線程里執(zhí)行,總要有辦法能看到線程的執(zhí)行狀態(tài)吧。Mysql提供了下面的命令,給大家查看 SHOW [FULL] PROCESSLIST 出來(lái)的結(jié)果是長(zhǎng)下面這樣的 圖里Command 這一列,反應(yīng)的就是這個(gè)線程當(dāng)前的執(zhí)行狀態(tài)啦。我在這個(gè)線程的執(zhí)行過(guò)程中,狀態(tài)是會(huì)變化很多次。 你看圖里,有一個(gè) Sleep ,這是在告訴你線程正在等待客戶端發(fā)送新的請(qǐng)求。還有一個(gè)為Query ,這代表線程正在執(zhí)行查詢或者正在將結(jié)果發(fā)送給客戶端。 至于其他的,還有 Locked 、Sending data 等等,分別代表…額,好吧,嘮嘮叨叨了一大堆,大家居然木有嫌我煩,嗯,至于其他狀態(tài)的含義大家可以去Mysql官網(wǎng)查詢哦。 嗯,回到剛才的話題。我到達(dá)服務(wù)端后,Mysql要判斷我的前6個(gè)字符是否為select。并且,語(yǔ)句中不帶有 SQL_NO_CACHE 關(guān)鍵字,如果符合條件,就進(jìn)入查詢緩存。第一章 我和查詢緩存的那些事說(shuō)到查詢緩存,它其實(shí)是一個(gè)哈希表,它將執(zhí)行過(guò)的語(yǔ)句及其結(jié)果會(huì)以 key-value 對(duì)的形式,被直接緩存在內(nèi)存中。 當(dāng)然,如果我要繞過(guò)查詢緩存,也很簡(jiǎn)單。我可以像下面這么寫: Select SQL_NO_CACHE * from table 也可以將參數(shù)query_cache_type 設(shè)置成DEMAND 來(lái)繞過(guò)查詢緩存。可是,有一天查詢緩存悲傷的對(duì)我說(shuō):'你將來(lái)再也看不到我了,我已經(jīng)被歷史淘汰了,Mysql8.0版本開(kāi)始就沒(méi)有我了!' 只要有對(duì)一個(gè)表的更新,這個(gè)表上所有的查詢緩存都會(huì)被清空 SQL任何字符上的不同,如空格,注釋,都會(huì)導(dǎo)致緩存不命中 因此,我能想到用查詢緩存的表,只有一種情況,那就是配置表。其他的業(yè)務(wù)表,根本是無(wú)法利用查詢緩存的特性,或許Mysql團(tuán)隊(duì)也是覺(jué)得查詢緩存的使用場(chǎng)景過(guò)于局限,就無(wú)情的將它剔除。 第二章 我和分析器的愛(ài)恨情仇(本文將解析器和預(yù)處理器統(tǒng)一稱為分析器) select username from userinfo 解析器:'好,好,好。我有兩個(gè)階段,我先對(duì)你進(jìn)行詞法分析,我將你從左到右一個(gè)字符、一個(gè)字符地輸入,然后根據(jù)構(gòu)詞規(guī)則識(shí)別單詞。你將會(huì)生成4個(gè)Token,如下所示。' 解析器:'接下來(lái)呢,進(jìn)行語(yǔ)法解析,判斷你輸入的這個(gè) SQL 語(yǔ)句是否滿足 MySQL 語(yǔ)法。然后生成下面這樣一顆語(yǔ)法樹(shù)。' 我:'如果語(yǔ)法不對(duì)呢?'解析器:'那你會(huì)收到一個(gè)提示如下!' You have an error in your SQL syntax 解析器:'順利生成語(yǔ)法樹(shù)以后,我就將你送往預(yù)處理器!' Unknown column xxx in ‘where clause’ 預(yù)處理器:'最后我再給你送去做權(quán)限驗(yàn)證,如果你沒(méi)有操作這個(gè)表的權(quán)限,會(huì)報(bào)下面這個(gè)錯(cuò)誤!' ERROR 1142 (42000): SELECT command denied to user 'root'@'localhost' for table 'xxx' (這個(gè)地方,大家可能有疑問(wèn),因?yàn)橛行┪恼抡f(shuō)是執(zhí)行器做的權(quán)限驗(yàn)證,可以直接拉到本文底部看說(shuō)明) 最后,這顆語(yǔ)法樹(shù)會(huì)傳遞給優(yōu)化器。 第三章 我和優(yōu)化器的動(dòng)人過(guò)往在告別了解析器后,我進(jìn)入了優(yōu)化器。 select t1.* 優(yōu)化器大哥:'我的任務(wù)就是幫你判斷一下怎么樣執(zhí)行更快,比如先查Table1 再查Table2 ,還是先查Table2 再查Table1 呢?判斷完如何執(zhí)行以后,生成執(zhí)行計(jì)劃就好啦!' 我很不信任的說(shuō)道:“哼,你就不會(huì)判斷失誤么!” 優(yōu)化器大哥:'那就要對(duì)SQL進(jìn)行改寫啦,比如你帶了 STRAIGHT_JOIN 關(guān)鍵字,長(zhǎng)下面這樣'select t1.* '那我就知道強(qiáng)制先找Table1 再關(guān)聯(lián)找Table2 啦,類似的例子還有很多,我就不一一列舉了!' ( STRAIGHT_JOIN 功能同join類似,但能讓左邊的表來(lái)驅(qū)動(dòng)右邊的表,能改表優(yōu)化器對(duì)于聯(lián)表查詢的執(zhí)行順序。)我說(shuō)道:'哇塞,如何編寫一個(gè)高效的SQL,真是一門學(xué)問(wèn)?。? 第四章 我和執(zhí)行器的悲情經(jīng)歷我:'執(zhí)行器大哥,你是用來(lái)做什么的?'執(zhí)行器:'就是根據(jù)執(zhí)行計(jì)劃來(lái)進(jìn)行執(zhí)行查詢啦。我就根據(jù)你的指令,逐條調(diào)用底層存儲(chǔ)引擎,逐步執(zhí)行。' MySQL 定義了一系列抽象存儲(chǔ)引擎API,以支持插件式存儲(chǔ)引擎架構(gòu)。Mysql實(shí)現(xiàn)了一個(gè)抽象接口層,叫做handler(sql/handler.h) ,其中定義了接口函數(shù),比如:,,ha_create 等等,存儲(chǔ)引擎需要實(shí)現(xiàn)這些接口才能被系統(tǒng)使用。末章 一些感慨最后一個(gè)階段,Mysql會(huì)將查詢結(jié)果返回客戶端。 一些疑問(wèn)這里關(guān)于權(quán)限驗(yàn)證究竟在哪個(gè)階段執(zhí)行,大家可能會(huì)有一些疑問(wèn)。 論點(diǎn)一:權(quán)限驗(yàn)證在執(zhí)行器中判斷從邏輯上說(shuō)不通一條查詢SQL經(jīng)過(guò)查詢緩存、分析器、優(yōu)化器,執(zhí)行器。如果到最后一個(gè)階段執(zhí)行器中才發(fā)現(xiàn)權(quán)限不足、那不是前面一系列流程白做了,Mysql應(yīng)該不至于這么傻吧~ 論點(diǎn)二:同《高性能Mysql》一書內(nèi)容不符該書209頁(yè)有一句話如下圖所示 該書也指明權(quán)限驗(yàn)證是在預(yù)處理器中執(zhí)行。本文中將預(yù)處理和解析器統(tǒng)一劃分為分析器的范疇。 論點(diǎn)三:同源碼不符我翻看了Mysql5.7.25這個(gè)版本的源碼,其在處理查詢這段的核心代碼如下在 sql_parse.cc 文件中,有這么一段代碼如下case SQLCOM_SELECT: 其中是進(jìn)行權(quán)限校驗(yàn)。而優(yōu)化器和執(zhí)行器是在execute_sqlcom_select 這個(gè)方法中。 當(dāng)然,大家有新的見(jiàn)解,歡迎留言。最后轉(zhuǎn)發(fā)到朋友圈是對(duì)我最大的支持! |
|
來(lái)自: 昵稱16619343 > 《區(qū)塊鏈》