《MySQL》系列 - select 語句是怎麼執行的?

2022-01-03 JavaFish

這篇文章之前發過,手賤刪錯了。再次分享出來,希望對你有幫助~

mysql 作為一個關係型資料庫,在國內使用應該是最廣泛的。也許你司使用 Oracle、Pg 等等,但是大多數網際網路公司,比如我司使用得最多的還是 Mysql,重要性不言而喻。

事情是這樣的,某天我司小胖問我執行 select * from table,資料庫底層到底發生了啥?從而我們得到數據呢?以下把我給問住了,為此我查閱了大量的書籍、博客。於是就有了這篇文章。

假設現在我有張 user 表,只有兩列,一列 id 自增的,一列 name 是  varchar 類型。建表語句是這樣的:

CREATE TABLE IF NOT EXISTS `user`(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(100) NOT NULL,
   PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

小胖的問題就是下面這個語句的執行過程。

select * from user where id = 1; 

01 mysql 架構概覽

要想理解這個問題就必須要知道 mysql 的內部架構。為此,我畫了張 mysql 的架構圖(你也可以理解為 sql 查詢語句的執行過程),如下所示:

Mysql 架構圖

首先 msql 分為 server 層和存儲引擎層兩個部分。server 層包括四個功能模塊,分別是:連接器、查詢緩存、優化器、執行器。這一層負責了 mysql 的所有核心工作,比如:內置函數、存儲過程、觸發器以及視圖等。

而存儲引擎層則是負責數據的存取。注意,存儲引擎在 mysql 是可選的,常見的還有: InnoDB、MyISAM 以及 Memory 等,最常用的就是 InnoDB。現在默認的存儲引擎也是它(從 mysql 5.5.5 版本開始),大家可以看到我上面的建表語句就是指定了 InnoDB 引擎。當然,你不指定的話默認也是它。

由於存儲引擎是可選的,所以 mysql 中,所有的存儲引擎其實是共用一個 server 層的。回到正題,我們就以這張圖的流程來解決一下小胖的問題。

1.1 連接器

首先,資料庫要執行 sql,肯定要先連接資料庫吧。這部分工作就是由連接器完成。它負責校驗帳戶密碼、獲取權限、管理連接數,最終與客戶端建立連接等工作。mysql 連結資料庫是這樣寫的:

mysql -h 127.0.0.1 -P 3306 -u root -p
# 127.0.0.1 : ip 3306 : 埠 root : 用戶名

運行命令之後需要輸入密碼,當然也可以跟在 -p 後面。不過不建議這麼做,會有密碼洩露的風險。

輸入命令後,連接器根據你的帳戶名密碼驗證身份。這是會出現兩種情況:

帳號或密碼不對,服務端會返回一個 "ERROR 1045 (28000): Access denied for user 'root'@'127.0.0.1' (using password: YES)" 的錯誤,退出連接。驗證通過,連接器就會到權限表查出你的權限。之後你有啥權限都要通過這時讀到的權限進行判斷

注意,我說的是此時查到的權限。就算你用管理員帳號修改了當前用戶的權限,此時已連接上的當前用戶不受影響,必須要重啟 mysql 新的權限才會生效

1.1.1 查看連接狀態

連接完成,如果後續沒有做任何事情,這個連接就處於空閒狀態。你可以用 show processlist; 命令查看 mysql 的連接信息,如下圖,我的資料庫連接都是 Sleep 狀態的,除了執行 show processlist 操作的連接。

show processlist;1.1.2 控制連接

如果客戶端太長時間沒有操作,此連接將會自動斷開。這個時間默認是 8 小時,由參數 wait_timeout 控制。如果斷開以後繼續操作就會收到 "Lost connection to MySQL server during query" 的錯誤。這時就必須重連才能執行請求。

資料庫裡面有長短連接之分,長連接:連接成功後不斷有請求,就會一直使用同一連接。短連接:每次執行完幾次請求就斷開連接,下次需要再建立。

由於建立連接是比較耗時的操作,所以建議使用長連接。但這會有個問題長連接一直連著就會導致內存佔用過大,被系統強行沙雕。從而導致 MySQL 異常重啟。如何解決呢?兩個方法:

定期斷開長連接。使用特定時間,或者程序判斷執行一個佔用內存大的操作後,斷開連接。之後需要操作就重連。mySQL 5.7 或以上版本,可以在每次執行一個佔用內存大的操作後,執行 mysql_reset_connection 來重新連接資源,此時不需重連或重新做權限認證,但會把連接狀態恢復到剛創建完時。1.2 查詢緩存

連接建立以後可以執行 select 語句了。這就會來到第二步:查詢緩存。

查詢緩存中存儲的數據是 key-value 的形式,key 是查詢語句,value 是查詢的結果。邏輯是這樣的:先看看查詢緩存有沒該語句對應的 value?有則直接取出返回客戶端,無則繼續到資料庫執行語句。查出結果後會放一份到緩存中,再返回客戶端。

你可能發現緩存真的香,但是並不建議使用查詢緩存,因為有弊端。查詢緩存的失效非常頻繁,只有某個表有更新。它馬上失效了,對於經常更新的表來說,命中緩存的概率極低。它僅僅適用於那些不經常更新的表

而 MySQL 似乎也考慮到這點了。提供了 query_cache_type 參數,把它設置為 DEMAND 就不再使用緩存。而對於要使用緩存的語句則可用 SQL_CACHE 顯示指定,像這樣:

select SQL_CACHE * from user where id = 1;

PS:MySQL 8.0 及以上版本把查詢緩存刪掉了,之後再也沒有這塊功能了。

1.3 分析器

如果沒有命中緩存就進入分析器,這裡就是對 sql 進行分析。分析器會做詞法分析。你輸入的 sql 是啥,由啥組成,MySQL 都需要知道它們代表什麼。

首先根據 "select" 識別出這是查詢語句。字符串 "user" 識別成 "表名 user"、字符串 "id" 識別成 "列名 id"。

之後進行語法分析,它會根據輸入的語句分析是不是符合 MySQL 的語法。具體表現就是 select、where、from 等關鍵字少了個字母,明顯不符合 MySQL 語法,這次就會報個語法錯誤的異常:它一般會提示錯誤行數,關注 "use near" 後面即可。

語法錯誤1.4 優化器

過了分析器,就來到了優化器。MySQL 是個聰明的仔,再執行之前會自己優化下客戶端傳過來的語句,看看那種執行起來不那麼佔內存、快一點。比如下面的 sql 語句:

select * from user u inner join role r on u.id = r.user_id where u.name = "狗哥" and r.id = 666

它可以先從 user 表拿出 name = "狗哥" 記錄的 ID 值再跟 role 表內連接查詢,再判斷 role 表裡面 id 的值是否 = 666

也可以反過來:先從 role 表拿出 id = 666 記錄的 ID 值再跟 user 表內連接查詢,在判斷 user 表裡面的 name 值是否 = "狗哥"。

兩種方案的執行結果是一樣的,但是效率不一樣、佔用的資源也就不一樣。優化器就是在選擇執行的方案。它優化的是索引應該用哪個?多表聯查應該先查哪個表?怎麼連接等等

1.5 執行器

分析器知道了做啥、優化器知道了應該怎麼做。接下來就交給執行器去執行了。

開始執行,判斷是否有相應的權限。比如該帳戶對 user 表沒權限就返回無權限的錯誤,如下所示:

select * from user where id = 1;

ERROR 1142 (42000): SELECT command denied to user 'nasus'@'localhost' for table 'user'

PS:如果命中緩存沒走到執行器這裡,那麼在返回查詢結果時做權限驗證。

回到正題,如果有權限,繼續打開表執行。執行器會根據表定義的引擎去使用對應接口。比如我們上面的 sql 語句執行流程是這樣的:

走 id 索引、調用 InnoDB 引擎取 "滿足條件的第一行" 接口,再循環調用 "滿足條件的下一行" 接口(這些接口都是存儲引擎定義好的),直到表中不再有滿足條件的行。執行器就將上述遍歷得到的行組成結果集返回給客戶端。

對於 id 不是索引的表,執行器只能調用 "取表記錄的第一行" 接口,再判斷 id 是否 = 1。如果不是則跳過,是則存在結果集中;再調存儲引擎接口取 "下一行",重複判斷邏輯,直到表的最後一行。

至此,整個 SQL 的執行流程完畢,小胖懂了嗎?

巨人的肩膀

https://time.geekbang.org/column/article/68319總結

本文通過一條簡單的 SQL 查詢語句,引出 MySQL 的結構以及這條 sql 查詢語句的執行流程。相信你看完會對 SQL 有更深的理解。

小福利

如果看到這裡,喜歡這篇文章的話,請幫點個好看。微信搜索一個優秀的廢人,關注後回復電子書送你 1000+ 本編程電子書 ,包括 C、C++、Java、Python、GO、Linux、Git、資料庫、設計模式、前端、人工智慧、面試相關、數據結構與算法以及計算機基礎,詳情看下圖。回復 1024 送你一套完整的 java 視頻教程。

相關焦點

  • 學習MySQL的select語句
    select語句可 以用回車分隔$sql="select * from article where id=1"和  $sql="select * from article where id=1">都可以得到正確的結果,但有時分開寫或許能 更明了一點,特別是當sql語句比較長時。
  • 揭秘一條select語句,在MySQL中權限訪問控制內幕
    其權限驗證大概流程如下所示舉一個select語句的例子select id,name from test.t4 where status='delete'用戶要執行這個select語句整個流程第一步:應用首先需要連接mysql資料庫
  • mysql資料庫select查詢語句
    select的相關語句在mysql中用的非常多,介紹一下。select語句的介紹1 select * from 表名;*(代表表中的所有欄位)2 select distinct 欄位名 from* from 表名 limit 0,3;從第一條開始 找三條數據7 聚合函數sum 函數 select sum(欄位名) from 表名; 求某個欄位的和avg 函數 select avg(欄位名) from 表名; 求某個欄位的平均值
  • MySQL-SQL語句執行流程
    select 語句了。執行邏輯就會來到第二步:查詢緩存。MySQL 拿到一個查詢請求後,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。如果你的查詢能夠直接在這個緩存中找到 key,那麼這個value 就會被直接返回給客戶端。如果語句不在查詢緩存中,就會繼續後面的執行階段。
  • Mysql中一條SQL查詢語句是如何執行的?
    2.查詢流程解析select * from table1 where ID=10;這條語句相信大家再熟悉不過了,下面我們就看看這一條語句在mysql中是怎麼執行的。第一步:一條sql語句要經過連接器,客戶端要和mysql建立連接。
  • 面試官:你知道select語句和update語句分別是怎麼執行的嗎?
    來自:黎社編程(ID:  LDCldc123095)最近有粉絲面試網際網路公司被問到:你知道select語句和update語句分別是怎麼執行的嗎?,要我寫一篇這兩者執行SQL語句的區別,這不就來了。分析器分析器主要有兩步:(1)詞法分析(2)語法分析詞法分析主要執行提煉關鍵性字,比如select,提交檢索的表,提交欄位名,提交檢索條件,確定該語句是select還是update或者是delete語句。
  • 「MySQL系列」分析Sql執行時間及查詢執行計劃(附資料庫和一千萬數據)
    Netty系列文章先告一段落了,因為這些知識已經滿足我當前工作的需求。後續項目中遇到問題和問題解決,會繼續更新Netty相關文章。哈哈哈 囉嗦的話有點多。接下來我將更新MySQL系列的文章,主要涉及MySQL的索引、調優、整體架構、引擎、分庫分表、擴容、索引的深入探究等等。感興趣的可以持續關注,不感興趣的略過。
  • 面試官:你知道 select 語句和 update 語句分別是怎麼執行的嗎?
    select語句和update語句分別是怎麼執行的嗎?,要我寫一篇這兩者執行SQL語句的區別,這不就來了。總的來說,select和update執行的邏輯大體一樣,但是具體的實現還是有區別的。分析器分析器主要有兩步:(1)詞法分析(2)語法分析詞法分析主要執行提煉關鍵性字,比如select,提交檢索的表,提交欄位名,提交檢索條件,確定該語句是select還是update或者是delete語句。
  • mysql的一個select需要經歷什麼查詢出結果
    語法分析報錯,詞法分析目的是識別出sql裡面的字符串代表什麼,語法分析的目的是識別出sql語句是否滿足mysql語法。對於表的操作權限驗證是在哪裡進行的?執行器驗證執行器的執行查詢語句的流程是什麼樣的?見下文為什麼不在優化器前做權限判斷?因為查詢的表不一定是sql語句,比如觸發器。此時優化器階段並不知道,只能在執行階段知道。
  • mysql常用sql語句總結
    ;update 表名 set列1=新值1 ,列2=新值2Where expr;(不加影響所有行);delete from 表名 where expr;select 列1,列2,.列n from 表名 where expr;select * from tmp where name is not null;mysql> select
  • 玩轉Mysql系列 - 第6篇:select查詢基礎篇
    這是Mysql系列第6篇。環境:mysql5.7.25,cmd命令中進行演示。DQL(Data QueryLanguage):數據查詢語言,通俗點講就是從資料庫獲取數據的,按照DQL的語法給資料庫發送一條指令,資料庫將按需求返回數據。DQL分多篇來說,本文屬於第1篇。
  • 為什麼要避免使用「CREATE TABLE AS SELECT」語句
    SQL語句「create table <table_name> as select ...」用於創建普通表或臨時表,並物化select的結果。某些應用程式使用這種結構來創建表的副本。一條語句完成所有工作,因此您無需創建表結構或使用其他語句來複製結構。
  • 一條查詢SQL在MySQL中是怎麼執行的
    使用一段時間,或者程序裡面判斷執行過一個佔用內存的大查詢後,斷開連接,之後要查詢再重連。如果MySQL版本是5.7或以上版本,可以在每次執行一個比較大的操作後,通過執行mysql_reset_connection來重新初始化連接資源。
  • MySQL LOAD DATA 語句詳解
    熟悉MySQL體系結構,Innodb存儲引擎,喜好專研開源技術,追求完美對於MySQL 的LOAD DATA語句來說,它使用的頻率並不高,因為它的適用場景比較窄。但是,由於它能夠加載按照指定分隔符分割的純文本數據,通過MySQL Server發行軟體包中提供的mysqlimport工具還能實現批量導入純文本數據。
  • MySQL的insert into select 引發鎖表
    上周五HaC我要上線,有一個腳本需要執行,執行前需要備份一個表。運維大佬:「這個表的備份為什麼要這麼久,,??」1秒過去了……2秒過去了……期間運營反饋系統出現大量訂單超時情況。大佬找到我,問:「你怎麼備份的?」我:「insert into select * from 呀!」大佬:「??
  • MySQL資料庫Insert語句7種寫法
    創建測試表在介紹Insert語句之前,先創建一張測試表mysql> show create table t_test_2\G;*************************** 1. row *************************** Table: t_test_2Create Table: CREATE TABLE
  • MySQL的SQL語句 - 數據操作語句(7)- INSERT SELECT 語句
    SELECT 語句,可以從 SELECT 語句的結果中快速地將許多行插入到表中,該語句可以從一個或多個表中進行選擇。FROM t 語句。因為在同一語句中不能引用兩次臨時表。出於同樣的原因,當t是臨時表時,不能使用 INSERT INTO t ... TABLE t 語句。● 為了確保二進位日誌可以用來重新創建原始表,MySQL 不允許 INSERT ... SELECT 或 INSERT ... TABLE 語句並發插入。
  • 生產MySQL資料庫執行一次analyze採集信息,應用炸了
    MySQL資料庫看了一下執行計劃,發現執行計劃不正確,第一反應就是其中的一個表的統計信息不準確,導致了SQL語句的執行計劃不對,從高效的查詢SQL變成了慢SQL。定位到問題之後,自然是 analyze 一下,重新採集信息,這個時候,卻發現 analyze 表上的所有 select 突然卡住了,不返回任何結果,然後應用就炸了,各種告警簡訊。故障復盤當時執行analyze操作的是一個slave庫,受影響基本是select查詢,所以在這裡模擬的是查詢操作。
  • 深度剖析,原來大名鼎鼎的MySQL是這樣執行的!
    因為學習和了解mysql是至關重要的,那麼當我們在客戶端發起一個sql到出現詳細的查詢數據,這其中究竟經歷了什麼樣的過程?mysql服務端是如何處理請求的,又是如何執行sql語句的?from where這些關鍵詞提取和匹配出來,mysql會自動判斷關鍵詞和非關鍵詞,將用戶的匹配欄位和自定義語句識別出來。
  • MySQL聊聊SELECT必須知道的基礎知識
    一、前言select語句可以說是mysql中最常用的語句了,除了select還有insert、delete、update等關鍵詞,這些關鍵詞是mysql的保留詞,我們在定義表名,欄位名,變量名的時候不要使用這些保留詞。