十四、mysql語句執行過程和存儲引擎

本文轉載至微信公衆號:我們都是小青蛙

一、準備知識

我們回過頭來再仔細想想使用MySQL的完整過程:

啓動MySQL服務器程序。
啓動MySQL客戶端程序並連接到服務器程序。
在客戶端程序中輸入一些命令語句發送到服務器程序,服務器程序收到這些請求後,會根據請求的內容來操作具體的數據。
也就是說,MySQL服務器程序纔是真實數據的管理者,它負責解析各個客戶端發來的各種請求並返回相應的執行結果!

我們知道計算機很牛逼,在一臺計算機上可以同時運行多個程序,比如微信、QQ、音樂播放器、文本編輯器啥的,每一個運行着的程序也被稱爲一個進程。我們的MySQL服務器程序本質上就是計算機上的一個進程,這個代表着MySQL服務器程序的進程也被稱爲MySQL數據庫實例,簡稱數據庫實例。

每個進程都有一個唯一的編號,稱爲進程號,英文名叫PID,這個編號是在我們啓動程序的時候由操作系統隨機分配的。比如你打開了計算機中的QQ程序,那麼操作系統會爲它分配一個唯一的進程號,如果你把這個程序關掉了,那操作系統就會把這個進程號回收後重新分配給別的進程,下一次再啓動QQ程序的時候分配的可能是另一個編號。每個進程都有一個名稱,這個名稱是編寫程序的人自己定義的,比如我們啓動的MySQL服務器進程的名稱爲mysqld,而MySQL客戶端進程的名稱爲mysql。

客戶端與服務器連接的過程

我們知道每啓動一個客戶端程序也是在計算機中啓動一個進程,客戶端程序向服務器程序發送請求並得到回覆的過程本質上是一個進程間通信的過程!MySQL支持下邊三種客戶端程序和服務器程序的通信方式

TCP/IP

在網絡環境下,每臺計算機都有一個唯一的IP地址,如果某個進程有進行網絡通信方面的需求,可以向操作系統申請一個端口號,這是一個整數值,它的取值範圍是0~65535。這樣在網絡中的其他進程就可以通過IP地址 + 端口號來確定這個進程,這樣進程之間就可以通過網絡進行通信了。

小貼士:
TCP/IP網絡體系結構是我們現在通用的一種網絡體系結構,其中的TCPIP是體系結構中兩個非常重要的網絡協議!

如果我們不手動的指定端口號的話,默認使用3306作爲數據庫實例的端口號。如果3306端口號已經被別的進程佔用了或者我們單純的想自定義該數據庫實例的端口號,那我們可以在啓動服務器的命令行裏添加–port 3307來明確指定一下端口號,比如這樣(以類Unix機器的啓動方式爲例):

mysql.server start --port 3307

如果客戶端程序使用TCP/IP網絡來連接到服務器程序的話,我們必須使用IP地址來作爲登陸命令中的主機名,如果客戶端程序和服務器程序在一臺計算機中的話,我們可以使用127.0.0.1來代表本機的IP地址。我們可以使用-P(大寫的P)在啓動客戶端的命令中指定連接服務器程序的端口號,就像這樣:

mysql -h127.0.0.1 -uroot -P3307 -p

多說一句,一般的工作環境中,MySQL服務器程序通常會被運行到一個獨立的機器中,所以其他客戶端程序只能通過TCP/IP網絡來與服務器程序進行通信。

命名管道和共享內存

如果我們的服務器程序和客戶端程序都運行在同一臺操作系統爲Windows的機器上的話,我們可以下邊這兩種方式來通信:

使用命名管道來進行線程間通信,不過需要在啓動服務器程序的命令中加上–enable-named-pipe參數,然後在啓動客戶端程序的命令中加入–protocal=pipe參數。

使用共享內存來進行線程間通信,不過需要在啓動服務器程序的命令中加上–shared-memory參數,在成功啓動服務器後,共享內存便成爲本地客戶端程序的默認連接方式,不過我們也可以在啓動客戶端程序的命令中加入–protocal=pipe參數來顯式的指定使用共享內存進行通信。

小貼士:
啥是個命名管道?啥是個共享內存?別問我,我也不知道,這一段兒純屬從別的書上抄過來的,嘮叨這個純屬爲了內容的完整性,不過不妨礙我們介紹MySQL的知識,不瞭解忽略它就好了~

Unix域套接字

如果我們的服務器程序和客戶端程序都運行在同一臺操作系統爲類Unix的機器上的話,我們可以使用Unix域套接字來進行線程間通信。

如果我們在啓動客戶端程序的時候指定的主機名爲localhost,或者指定了–protocal=socket的啓動參數,那服務器程序和客戶端程序之間就可以通過Unix域套接字來進行通信了。這個所謂的Unix域套接字其實是一個文件,它的默認文件路徑是/tmp/mysql.sock,其實通信的過程就是一個進程往文件中寫數據,另一個從文件中讀數據,這就起到了通信的效果。如果你不想用Unix域套接字的默認文件路徑,可以在啓動客戶端程序的時候指定-S參數來明確指定它的路徑,比如這樣:

mysql -hlocalhost -uroot -S /Users/wangqingfeng/mysql.sock -p

這樣該客戶端程序和服務器程序就可以通過路徑爲/Users/wangqingfeng/mysql.sock的Unix域套接字文件進行通信了。

二、服務器處理客戶端請求

其實不論客戶端程序和服務器程序是採用哪種方式進行通信,最後實現的效果都是:客戶端程序向服務器程序發送一段文本(MySQL語句),服務器程序處理後再向客戶端返回一段文本(處理結果)。那服務器程序對客戶端發送的請求做了什麼處理,才能產生最後的處理結果呢?我們以比較複雜的查詢請求爲例來畫個圖展示一下大致的過程:
在這裏插入圖片描述
在這裏插入圖片描述
從圖中我們可以看出,大體來說,MySQL 可以分爲 Server 層和存儲引擎層兩部分。
Server 層包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等。
而存儲引擎層負責數據的存儲和提取。

連接器--連接管理

客戶端程序可以採用我們上邊介紹的TCP/IP、命名管道和共享內存、Unix域套接字這幾種方式之一來與服務器程序建立連接,服務器程序會緩存一些線程,每當有客戶端連接進來的時候,會爲這個客戶端程序分配一個線程來處理它發過來的請求。

小貼士:

創建和銷燬線程會耗費很多性能,所以在服務器端維護許多線程可以減少這部分的性能損耗。但是服務器端維護的線程數目是有限的,如果在短時間內有超級多的客戶端連接進來的話,會有一部分因爲獲取不到線程而進入等待狀態,直到有的客戶端斷開連接後這些等待的客戶端纔可以被分配到線程。
在客戶端程序發起連接的時候,需要攜帶主機信息、用戶名、密碼,服務器程序會對客戶端程序提供的這些信息進行認證,如果認證失敗,服務器程序會拒絕連接。另外,如果客戶端程序和服務器程序不運行在一臺計算機上,我們還可以採用使用了SSL(安全套接字)的網絡連接進行通信,來保證數據傳輸的安全性。

優化與執行

當客戶端程序成功的與服務器程序建立連接之後,就可以把文本命令發送到服務器程序了。這個部分大致需要需要查詢緩存、語法解析、查詢優化這幾個步驟來完成,我們詳細來看。

查詢緩存

如果我問你9+8×16-3×2×17的值是多少,你可能會用計算器去算一下,或者牛逼一點用心算,最終得到了結果35,如果我再問你一遍9+8×16-3×2×17的值是多少,你還用再傻呵呵的算一遍麼?我們剛剛已經算過了,直接說答案就好了。MySQL服務器程序處理查詢請求的過程也是這樣,會把剛剛處理過的查詢請求和結果緩存起來,如果下一次有一模一樣的請求過來,直接從緩存中查找結果就好了,就不用再傻呵呵的去底層的表中查找了。

當然,MySQL服務器並沒有人聰明,如果兩個查詢請求在任何字符上的不同(例如:空格、註釋),都會導致緩存不會命中。另外,如果查詢請求中包含系統函數、存儲函數、自定義變量、mysql庫中的系統表,那這個請求就不會被緩存,以函數舉例,可能同樣的函數的兩次調用會產生不一樣的結果,比如函數NOW,每次調用都會產生最新的當前時間,如果在一個查詢請求中調用了這個函數,那即使查詢請求的文本信息都一樣,那不同時間的兩次查詢也應該得到不同的結果,如果在第一次查詢時就緩存了,那第二次查詢的時候直接使用第一次查詢的結果就是錯誤的!

不過既然是緩存,那就有它緩存失效的時候。MySQL的緩存系統會監測涉及到的每張表,只要該表的結構或者數據被修改,那與該表有關的緩存都會失效!

分析器--語法解析

如果緩存沒有命中,接下來就需要進入正式的查詢階段了。首先客戶端程序發送過來的請求只是一段文本而已,MySQL服務器程序首先要對這段文本做分析,判斷請求的語法是否正確,然後從文本中要查詢的表、各種查詢條件都提取出來。

如何從指定的文本中提取出我們需要的信息,這其實是一個編譯問題,這個過程首先會把指定的文本根據語法規則來驗證和解析成一顆語法樹,吧啦吧啦,啥是個編譯?咋弄出來的樹?這些問題不屬於我們討論的範疇,如果想知道更多,等我~

優化器

經過了分析器,MySQL 就知道你要做什麼了。在開始執行之前,還要先經過優化器的處理。
根據語法解析,服務器程序獲得到了需要的信息,比如查詢列表是什麼,表是哪個,搜索條件是什麼等等,但光有這些是不夠的,因爲我們寫的MySQL語句可能執行起來效率並不是很高,MySQL的優化程序會對我們的語句做一些優化,如外連接轉換爲內連接、表達式簡化、子查詢的轉爲連接、使用索引吧啦吧啦的一堆東西,這部分我們後邊會詳細嘮叨,現在你只需要知道在MySQL服務器程序處理請求的過程中有這麼一個步驟就好了。

執行器

MySQL 通過分析器知道了你要做什麼,通過優化器知道了該怎麼做,於是就進入了執行器階段,開始執行語句。
開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤,如下所示 (在工程實現上,如果命中查詢緩存,會在查詢緩存返回結果的時候,做權限驗證。查詢也會在優化器之前調用 precheck 驗證權限)。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user ‘b’@‘localhost’ for table ‘T’
如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口

存儲引擎

截止到目前,還沒有真正的去訪問真實的數據表,MySQL把數據的存儲和提取操作都封裝到了一個叫存儲引擎的模塊裏,我們知道表是由一行一行的記錄組成的,但這只是一個邏輯上的概念,物理上如何表示記錄,怎麼從表中讀取數據,怎麼把數據寫入具體的物理存儲器上,這都是存儲引擎負責的事情。爲了實現不同的功能,MySQL提供了各式各樣的存儲引擎,不同存儲引擎管理的表結構可能不同,採用的存取算法也可能不同。不過這些存儲引擎都向上邊的服務層提供統一的調用接口,也就是對於我們使用者來說,如果我們需要使用某個存儲引擎提供的特定功能,只需要簡單的切換表的存儲引擎就可以了。

小貼士:
爲什麼叫引擎呢?因爲裝逼唄~ 其實這個存儲引擎以前叫做表處理器,後來人們覺得太土,就改成了存儲引擎的叫法,它的功能就是接收上層傳下來的指令,然後對錶中的數據進行提取或寫入操作
所以在服務器程序完成了查詢優化後,執行器只需調用底層存儲引擎提供的調用接口,獲取到數據後返回給客戶端程序就好了。

三、常用存儲引擎

MySQL支持非常多種存儲引擎,我這先列舉一些:

存儲引擎 描述
ARCHIVE 用與數據存檔(行被插入後不能再修改)
BLACKHOLE 丟棄寫操作,讀操作會返回空內容
CSV 在存儲數據時,以逗號分隔各個數據項
FEDERATED 用來訪問遠程表
InnoDB 具備外鍵支持的事務存儲引擎
MEMORY 置於內存的表
MERGE 用來管理多個MyISAM表構成的表集合
MyISAM 主要的非事務處理存儲引擎
NDB MySQL集羣專用存儲引擎

這麼多我們怎麼挑啊,哈哈,你多慮了,其實我們最常用的就是InnoDB和MyISAM,有時會提一下Memory。其中InnoDB是MySQL默認的存儲引擎。

關於存儲引擎的一些操作

查看當前服務器程序支持的存儲引擎

我們可以用下邊這個命令來查看當前服務器程序支持的存儲引擎:

SHOW ENGINES;
來看一下調用效果:

在這裏插入圖片描述
其中的Support列表示該存儲引擎是否可用,DEFAULT值代表是當前服務器程序的默認存儲引擎。Comment列是對存儲引擎的一個描述,英文的,將就着看吧。Transactions列代表該存儲引擎是否支持事務處理。XA列代表着該存儲引擎是否支持分佈式事務。Savepoints代表着該列是否支持部分事務回滾。

設置表的存儲引擎

我們前邊說過,存儲引擎是負責對錶中的數據進行提取和寫入工作的,我們可以爲不同的表設置不同的存儲引擎,也就是說不同的表可以有不同的物理存儲結構,不同的提取和寫入方式。

創建表時指定存儲引擎

我們之前創建表的語句都沒有指定表的存儲引擎,那就會使用默認的存儲引擎InnoDB(當然這個默認的存儲引擎也是可以修改的,我們在後邊的章節中再說怎麼改)。如果我們想顯式的指定一下表的存儲引擎,那可以這麼寫:

CREATE TABLE 表名(
建表語句;
) ENGINE = 存儲引擎名稱;
比如我們想創建一個存儲引擎爲MyISAM的表可以這麼寫:

mysql> CREATE TABLE engine_demo_table(
    ->     i int
    -> ) ENGINE = MyISAM;
Query OK, 0 rows affected (0.02 sec)
mysql>

修改表的存儲引擎

如果表已經建好了,我們也可以使用下邊這個語句來修改表的存儲引擎:

ALTER TABLE 表名 ENGINE = 存儲引擎名稱;
比如我們修改一下engine_demo_table表的存儲引擎:

mysql> ALTER TABLE engine_demo_table ENGINE = InnoDB;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql>

這時我們再查看一下engine_demo_table的表結構:

mysql> SHOW CREATE TABLE engine_demo_table\G
*************************** 1. row ***************************
       Table: engine_demo_table
Create Table: CREATE TABLE `engine_demo_table` (
  `i` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
mysql>

可以看到該表的存儲引擎已經改爲InnoDB了。

四、總結

以查詢請求爲例,服務器程序處理客戶端發送過來的請求大致分爲三部分:

第一部分:連接管理。

主要是負責連接的建立與信息的認證。

第二部分:
查詢緩存
語法解析
查詢優化
執行
第三部分:存儲引擎。
主要負責對底層數據表中的數據進行提取和寫入工作。

MySQL支持的存儲引擎有好多好多種,它們在完成不同的功能上各有優劣,我們常用的就是InnoDB和MyISAM,其中InnoDB是服務器程序的默認存儲引擎。

一些常用的關於存儲引擎的用法如下:

查看當前服務器程序支持的存儲引擎:

SHOW ENGINES;
創建表時指定表的存儲引擎:

CREATE TABLE 表名(
建表語句;
) ENGINE = 存儲引擎名稱;
修改表的存儲引擎:

ALTER TABLE 表名 ENGINE = 存儲引擎名稱;

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