MySQL系列之執(zhí)行SQL 語句時(shí)發(fā)生了什么?
前言
當(dāng)我們用 navicat、mysql workbench 等mysql 的客戶端執(zhí)行一條sql語句后,我們就能得到相應(yīng)的結(jié)果。例如:
那么這個(gè)過程發(fā)生了什么呢?
執(zhí)行一條sql 就是一次Rpc的調(diào)用
mysql 是一個(gè)客戶端、服務(wù)端的架構(gòu)。我們平時(shí)使用的大部分程序app其實(shí)是由兩部分組成的,一部分是客戶端程序,一部分是服務(wù)器程序。
以我們常用的微信、qq 為例。我們手機(jī)里面裝的客戶端,機(jī)房的服務(wù)器中運(yùn)行著server端。我們平時(shí)發(fā)信息都是其實(shí)都是用客戶端和服務(wù)端打交道。比如你和你女朋友發(fā)信息的過程:
- 消息被客戶端包裝了一下,添加了發(fā)送者和接收者信息,然后從你的微信客戶端傳送給微信服務(wù)器;
- 微信服務(wù)器從消息里獲取到它的發(fā)送者和接收者,根據(jù)消息的接收者信息把這條消息送達(dá)到你女朋友的微信客戶端,你女朋友的微信客戶端里就顯示出你給他發(fā)了一條消息。
mysql的使用過程跟這個(gè)是一樣的,它的服務(wù)器程序直接和我們存儲(chǔ)的數(shù)據(jù)打交道,然后可以有好多客戶端程序連接到這個(gè)服務(wù)器程序,發(fā)送增刪改查的請(qǐng)求,然后服務(wù)器就響應(yīng)這些請(qǐng)求,從而操作它維護(hù)的數(shù)據(jù)。
主要流程如下:
所以,一條sql的執(zhí)行,就是一次rpc的調(diào)用。后面有時(shí)間也會(huì)分享RPC 相關(guān)的東西,一起交流學(xué)習(xí)!
服務(wù)器怎么處理客戶端請(qǐng)求
不管我們用了哪種客戶端和服務(wù)器進(jìn)程是采用哪種方式進(jìn)行通信,最后實(shí)現(xiàn)的效果都是:客戶端進(jìn)程向服務(wù)器進(jìn)程發(fā)送一段文本(MySQL語句),服務(wù)器進(jìn)程處理后再向客戶端進(jìn)程發(fā)送一段文本(處理結(jié)果)。主要過程如下:
從圖中我們可以看出,服務(wù)器程序處理來自客戶端的查詢請(qǐng)求大致需要經(jīng)過三個(gè)部分,分別是 連接管理、解析優(yōu)化與執(zhí)行、存儲(chǔ)引擎三個(gè)部分。其中連接管理、解析優(yōu)化與執(zhí)行常常被分為mysql的 server 層。
連接管理
客戶端進(jìn)程可以采用TCP/IP、命名管道或共享內(nèi)存、Unix域套接字這幾種方式之一來與服務(wù)器進(jìn)程建立連接。
對(duì)連接的管理也使用了池化技術(shù):每當(dāng)有一個(gè)客戶端進(jìn)程連接到服務(wù)器進(jìn)程時(shí),服務(wù)器進(jìn)程都會(huì)創(chuàng)建一個(gè)線程來專門處理與這個(gè)客戶端的交互,當(dāng)該客戶端退出時(shí)會(huì)與服務(wù)器斷開連接,服務(wù)器并不會(huì)立即把與該客戶端交互的線程銷毀掉,而是把它緩存起來,在另一個(gè)新的客戶端再進(jìn)行連接時(shí),把這個(gè)緩存的線程分配給該新客戶端。這樣就起到了不頻繁創(chuàng)建和銷毀線程的效果,從而節(jié)省開銷。
查詢緩存
由于表經(jīng)常更新,查詢緩存的失效頻繁,查詢緩存往往利大于弊。,MySQL 8.0 版本開始直接將查詢緩存的整塊功能刪掉了。
語法解析
如果查詢緩存沒有命中,接下來就需要進(jìn)入正式的查詢階段了。因?yàn)榭蛻舳顺绦虬l(fā)送過來的請(qǐng)求只是一段文本而已,所以MySQL服務(wù)器程序首先要對(duì)這段文本做分析,判斷請(qǐng)求的語法是否正確,然后從文本中將要查詢的表、各種查詢條件都提取出來放到MySQL服務(wù)器內(nèi)部使用的一些數(shù)據(jù)結(jié)構(gòu)上來。
查詢優(yōu)化
經(jīng)過了語法解析,MySQL 就知道你要做什么了。在開始執(zhí)行之前,還要先經(jīng)過查詢優(yōu)化的處理。優(yōu)化處理是指在表里面有多個(gè)索引的時(shí)候,決定使用哪個(gè)索引;或者在一個(gè)語句有多表關(guān)聯(lián)(join)的時(shí)候,決定各個(gè)表的連接順序。我們可以使用EXPLAIN語句來查看某個(gè)語句的執(zhí)行計(jì)劃 。
大部分優(yōu)化的邏輯是基于成本的優(yōu)化。在MySQL中一條查詢語句的執(zhí)行成本是由兩個(gè)方面組成的 :
- I/O成本 :從磁盤到內(nèi)存這個(gè)加載的過程損耗的時(shí)間稱之為I/O成本。
- CPU成本 :讀取以及檢測(cè)記錄是否滿足對(duì)應(yīng)的搜索條件、對(duì)結(jié)果集進(jìn)行排序等這些操作損耗的時(shí)間稱之為CPU成本。
對(duì)于InnoDB存儲(chǔ)引擎來說 ,mysql規(guī)定讀取一個(gè)頁(yè)面花費(fèi)的成本默認(rèn)是1.0,讀取以及檢測(cè)一條記錄是否符合搜索條件的成本默認(rèn)是0.2 。
拿單表查詢來舉例,成本計(jì)算步驟如下:
- 根據(jù)搜索條件,找出所有可能使用的索引
- 計(jì)算全表掃描的代價(jià)
- 計(jì)算使用不同索引執(zhí)行查詢的代價(jià)
- 對(duì)比各種執(zhí)行方案的代價(jià),找出成本最低的那一個(gè)。
存儲(chǔ)引擎
- 截止到服務(wù)器程序完成了查詢優(yōu)化為止,還沒有真正的去訪問真實(shí)的數(shù)據(jù)表,MySQL服務(wù)器把數(shù)據(jù)的存儲(chǔ)和提取操作都封裝到了一個(gè)叫存儲(chǔ)引擎的模塊里。我們知道表是由一行一行的記錄組成的,但這只是一個(gè)邏輯上的概念,物理上如何表示記錄,怎么從表中讀取數(shù)據(jù),怎么把數(shù)據(jù)寫入具體的物理存儲(chǔ)器上,這都是存儲(chǔ)引擎負(fù)責(zé)的事情。為了實(shí)現(xiàn)不同的功能,MySQL提供了各式各樣的存儲(chǔ)引擎,不同存儲(chǔ)引擎管理的表具體的存儲(chǔ)結(jié)構(gòu)可能不同,采用的存取算法也可能不同。
- 為了管理方便,所以大部分人把連接管理、查詢緩存、語法解析、查詢優(yōu)化這些并不涉及真實(shí)數(shù)據(jù)存儲(chǔ)的功能劃分為MySQL server的功能,
- 把真實(shí)存取數(shù)據(jù)的功能劃分為存儲(chǔ)引擎的功能。
執(zhí)行器
執(zhí)行器就是各種不同的存儲(chǔ)引擎向上邊的MySQL server層提供統(tǒng)一的調(diào)用接口(也就是存儲(chǔ)引擎API),包含了幾十個(gè)底層函數(shù),像"讀取索引第一條內(nèi)容"、"讀取索引下一條內(nèi)容"、"插入記錄"等等。
比如執(zhí)行一條查詢sql 時(shí),開始執(zhí)行的時(shí)候,要先判斷一下你對(duì)這個(gè)表 有沒有執(zhí)行查詢的權(quán)限,如果沒有,就會(huì)返回沒有權(quán)限的錯(cuò)誤;
拿我們開頭的例子中,id字段沒有索引,那么執(zhí)行器的執(zhí)行流程是這樣的:
- 調(diào)用 InnoDB 引擎接口取這個(gè)表的第一行,判斷 ID 值是不是 10,如果不是則跳過,如果是則將這行存在結(jié)果集中;
- 調(diào)用引擎接口取“下一行”,重復(fù)相同的判斷邏輯,直到取到這個(gè)表的最后一行。
- 執(zhí)行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端。
對(duì)于有索引的表,執(zhí)行的邏輯也差不多。第一次調(diào)用的是“取滿足條件的第一行”這個(gè)接口,之后循環(huán)取“滿足條件的下一行”這個(gè)接口,這些接口都是引擎中已經(jīng)定義好的。但是對(duì)于 插入、刪除和修改的sql語句,還要涉及到redolog、undolog 和binlog 的操作。這個(gè)我們有空再聊。
所以只需按照生成的執(zhí)行計(jì)劃調(diào)用底層存儲(chǔ)引擎提供的API(執(zhí)行器),獲取到數(shù)據(jù)后返回給客戶端就好了。一條sql 語句就執(zhí)行完成了。

































