一條查詢SQL在MySQL中是怎麼執行的

2020-12-05 酷扯兒

本文轉載自【微信公眾號:五角錢的程式設計師,ID:xianglin965】經微信公眾號授權轉載,如需轉載與原文作者聯繫

平時我們使用的資料庫,看到的通常是一個整體,比如我們執行一條查詢SQL,返回一個結果集,卻不知道這條語句在MySQL內部是如何執行的,接下來我們就來簡單的拆解一下MySQL,看看MySQL是由哪些「零件」組成的,在這個過程中逐步的揭開MySQL的面紗,對MySQL有個深入的理解。這樣在我們以後遇到MySQL的一些異常或者問題的時候,就可以快速定位問題並解決問題。

下邊通過一張圖來看一下SQL的執行流程,從中可以清楚的看到SQL語句在MySQL的各個功能模塊中執行的過程。

從上圖來看,MySQL大體上可以分為Server層和存儲引擎層兩部分。

Server層:包含連接器、查詢緩存、分析器、優化器、執行器等,涵蓋了MySQL的大部分核心服務功能,以及所有的內置函數,所有的跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等,

存儲引擎層:負責數據的存儲和提取,其架構模式是插件式的,支持InnoDB、MyISAM、Memory等多個存儲引擎。其中最常用的存儲引擎是InnoDB,從MySQL5.5.5版本開始就成為了默認的存儲引擎。也就是在創建表的時候,如果不指定存儲引擎類型,默認就是使用InnoDB,如果需要使用別的存儲引擎,在創建表的時候在create table語句中使用engine = MyISAM,來指定使用M有ISAM引擎創建表。不同的存儲引擎的表數據存取方式不同,支持的功能也不相同,以後我們再慢慢分析。

從圖中我們可以看到Server層由多個組件,從連接器開始到執行器,接下來我們使用一條簡單的查詢語句,來依次分析每個組件的作用。

select * from T where ID = 10;

連接器

第一步,會先連接到這個資料庫上,這個時候首先遇到的是連接器。連接器是負責跟客戶端建立連接、獲取權限、維持和管理連接。一般連接命令是這樣寫的:

mysql -h$ip -P$port -u$user -p

輸入命令之後,就需要在交互對話中輸入密碼,密碼也可以直接寫在-p後面,但是這種操作一般是開發過程中,連接生產伺服器不建議這樣做,因為可能會導緻密碼洩露。

連接命令中的mysql是客戶端工具,用來和服務端建立連接,在完成經典的TCP握手後,連接器就開始認證身份,這個時候用到的就是輸入的用戶名和密碼。

用戶名或密碼不對時,就會報一個「Access denied for user」的錯誤,然後客戶端程序結束執行。如果用戶名密碼認證通過,連接器會到權限表裡面查出你這個用戶名的權限,之後這個連接裡面的權限判斷邏輯,都將依賴於此時讀到的權限,建立連接後權限就確定下來了,如果這個時候使用管理員帳號修改了這個用戶的權限,也是需要下次重新連接時生效。連接完成後,如果沒有後續操作,這個連接就處於空閒狀態,可以使用

show processlist

命令查詢,下圖中的Command列顯示為

Sleep

的這一行,就表示現在系統中有一個空閒連接。

客戶端如果長時間處於空閒狀態,連接器就會自帶將它斷開連接,這個時間由參數

wait_timeout

控制,默認值是8小時。

如果在斷開連接後,客戶端再次發送請求的話,就會收到一個錯誤提示:「Lost connection to MySQL server during query」。這個時候如果需要繼續操作資料庫,就需要重新連接然後再執行請求。

資料庫裡面有兩種連接分別是長連接和短連接,長連接是指連接成功後,如果客戶端持續有請求,則一直使用同一個連接,短連接則是指每次執行完很少的幾次查詢就斷開連接,下次查詢再重新建立一個連接。因為建立連接的過程通常比較複雜,所以建議儘量減少建立連接的動作,也就是儘量使用長連接而不是短連接。

當我們全部使用長連接後,會發現有時候MySQL專用內存漲的特別快,這是因為MySQL在執行過程中臨時使用的內存是管理在連接對象裡面的,這些資源會在連接斷開的時候才釋放,所以長時間使用長連接累計下來,可能導致內存佔用太大,被系統強行殺掉,也就是我們有時候看到的MySQL異常重啟。

這個問題也不是不能解決的,常用的方案主要有以下兩種:

定期斷開長連接。使用一段時間,或者程序裡面判斷執行過一個佔用內存的大查詢後,斷開連接,之後要查詢再重連。如果MySQL版本是5.7或以上版本,可以在每次執行一個比較大的操作後,通過執行mysql_reset_connection來重新初始化連接資源。這個過程不需要重連和重新做權限校驗,但是會將連接恢復到剛創建完成的狀態。查詢緩存

在建立完成後,就可以執行select語句了,執行邏輯就會來到查詢緩存。MySQL拿到一個查詢請求後,會先到緩存查查看看,如果之前執行過的語句就會將執行過的語句和結果以key-value對的形式,被直接存放在內存中,key是查詢語句,value是結果。如果查詢語句在緩存中可以查到這個key,就直接把結果返回給客戶端。如果語句不在緩存中,就會繼續執行後邊的階段。執行完成後,將執行結果存入緩存中。

但是,大部分情況下都不建議使用查詢緩存,這是因為查詢緩存往往弊大於利。

查詢緩存的失效非常頻繁,只要對表做一次更新操作,這個表上所有的查詢緩存都會被清空,因此經常會出現剛把結果放入緩存還沒使用,就被一個更新清空了,所以對於更新很頻繁的資料庫來說,查詢緩存的命中率很低。除非是很久才更新一次的數據表,比如系統配置表,那這張表上的查詢才適合使用查詢緩存。

MySQL提供了

query_cache_type

參數來設置是否查詢緩存,將該參數設置成

DEMAND

這樣對於默認的SQL語句都不使用查詢緩存,如果確定需要使用查詢緩存的語句,可以用

SQL_CACHE

來顯式指定,如下:

mysql> select SQL_CACHE * from T where ID = 2;

如果你使用的MySQL8.0版本就不用考慮這個問題,因為8.0版本開始徹底的沒有這個功能了。

分析器

接著上面一步,如果沒有命中查詢緩存,就開始真的執行語句了,首先MySQL需要知道你要做什麼,會對SQL語句進行解析。

分析器會先做「詞法分析」,你輸入的SQL語句中由多個字符串和空格組成,MySQL需要識別出裡面的字符串分別是什麼,代表什麼。如上邊的SQL語句,MySQL從你輸入的

select

關鍵字識別出來,這是查詢語句,它也會把字符串

T

識別成表名「T「,把字符串

ID

識別成」列ID「。

做完了這些識別之後,就會做」語法分析「,根據詞法分析的結果,語法分析會根據語法規則,判斷輸入的SQL語句是否滿足MySQL的語法要求。

如果SQL語句有問題,就會有」You have an error in your SQL syntax「的錯誤提醒

mysql> elect * from t where ID=1;

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1

一般語法錯誤會提示第一個出現錯誤的位置,user near緊接的內容就是報錯內容相關。

優化器

經過了分析器,MySQL就知道你要做什麼了,在執行之前,還要經過優化器處理。

優化器是在表裡有多個索引的時候,決定使用哪個索引;或者在一個語句中有多表關聯的時候,決定各個表的連接順序。如下的語句:

mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;

既可以先從t1表裡取出c=10的記錄的ID值,再根據ID值關聯到表t2,再判斷t2表裡的值是否等於20也可以先從t2表裡取出d = 20的記錄ID值,再根據ID關聯到t1表,再判斷t1表裡面c1 的值是否等於10這兩個執行的邏輯結果是一樣的,但是執行效率是不同的,優化器在這裡的作用就是決定選擇哪一種方案。優化器遵循的原則:儘可能掃描少的資料庫行記錄。

優化器階段完成後,這個SQL語句的執行方案就確定下來了,進入執行階段。

執行器

通過前面幾步操作,MySQL已經知道了你要做什麼,也優化了做的方式,就進入執行器階段,開始執行語句。開始執行的時候,要先判斷一下你對這個表有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤。這裡還有一個細節,如果在第二步查詢緩存的時候命中緩存,會在緩存返回結果的時候做權限校驗。查詢也會在優化器之前調用precheck驗證權限。

主要這裡是對表的權限進行校驗,而連接器是驗證用戶的身份。

如果有權限,就打開表繼續執行,打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。如我們這個例子的表T中,ID欄位沒有添加索引,那麼執行流程如下:

調用InnoDB引擎接口取這個表的第一行,判斷ID值是不是10,如果不是則跳過,如果是則將這一行放入結果集中。調用引擎接口取「下一行」,重複相同的判斷邏輯,直到這個表的最後一行。執行器將上述遍歷過程中所有滿足條件的行組成一個結果集返回給客戶端。到這裡,這個查詢SQL就執行完成了。

對於有索引的表,執行的邏輯大同小異,第一次調用的是「取滿足條件的第一行」這個接口,然後循環取「滿足條件的下一行」這個接口,這些接口都是引擎中定義好的。

在資料庫的慢查詢日誌中可以看到一個

rows_examined

的欄位,表示這個語句執行過程中掃描了多少行,這個值是在執行器每次調用引擎的時候累加的,有時候執行器調用一次,在引擎內部掃描了多行,隱藏引擎掃描行數跟

rows_examined

並不完全相同。

到這裡,對於一個SQL語句完整執行流程各個階段有了初步認識,對於我們後續深入學習MySQL有個基礎,最後分享一個小故事。

連接器:門衛,想進請出示準入憑證(工牌、邀請證明一類)。「你好,你是普通員工,只能進入辦公大廳,不能到高管區域」此為權限查詢。

分析器:「您需要在公司裡面找一張頭髮是黑色的桌子?桌子沒有頭髮啊!臣妾做不到」

優化器:「要我在A B兩個辦公室找張三和李四啊?那我應該先去B辦公室找李四,然後請李四幫我去A辦公室找張三,因為B辦公室比較近且李四知道張三具體工位在哪」

執行器:「好了,找人的計劃方案定了,開始行動吧,走你!糟糕,剛門衛大哥說了,我沒有權限進B辦公室」

相關焦點

  • mysql 版本號解釋_mysql workbench查詢mysql版本號 - CSDN
    statement模式下,每一條會修改數據的sql都會記錄在binlog中。不需要記錄每一行的變化,減少了binlog日誌量,節約了IO,提高性能。由於sql的執行是有上下文的,因此在保存的時候需要保存相關的信息,同時還有一些使用了函數之類的語句無法被記錄複製。row級別下,不記錄sql語句上下文相關信息,僅保存哪條記錄被修改。
  • mysql 矩陣類型專題及常見問題 - CSDN
    一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。隔離性是指當多個用戶並發訪問資料庫時,比如同時訪問一張表,資料庫每一個用戶開啟的事務,不能被其他事務所做的操作幹擾(也就是事務之間的隔離),多個並發事務之間,應當相互隔離。
  • mysql查詢前一周的數據_mysql查詢當天的數據 - CSDN
    mysql 昨天 一周前 一月前 一年前的數據 這裡主要用到了DATE_SUB,參考如下代碼如下:SELECT * FROM
  • Python連接MySQL資料庫方法介紹(超詳細!手把手項目案例操作)
    >cur.close() # 關閉遊標conn.close() # 關閉連接上述代碼中,實現了通過Python連接MySQL查詢所有的數據,並輸出前2條數據的功能。執行結果如下:('a', '趙大', '16')('b', '錢二', '16')mysql.connectormysql-connector-python:是MySQL官方的純Python驅動;mysql.connector
  • 請確保你查詢mysql資料庫時,sql語句沒有這麼寫_手機網易網
    所以大家在寫查詢的sql語句時為了讓語句執行效率高會讓語句能命中索引,或者新建合適的索引。  明明我sql語句where條件的欄位是符合索引,應該可以命中索引的,但是執行時卻沒有命中索引。  為什麼會這樣呢,是人性的……  額,串臺了,調回來。  要說明這個問題,大家先來比較一下下面的這兩個sql語句。  這兩個sql語句唯一的區別就是where條件中id對應的值一個加了引號,一個沒有加引號。
  • MySQL字符串截取 和 截取字符進行查詢
    通過mysql自帶的一些字符串截取函數,對數據進行處理,下面是我整理的字符串截取 和 截取字符進行查詢。一、MySQL中字符串的截取MySQL中有專門的字符串截取函數:其中常用的有兩種:substring_index(str,delim,count) 和concat 1.substring_index(str,delim,count) 函數的使用較為普遍。
  • python3.8操作(插入,刪除)mysql/MariaDB資料庫
    01主題大家好,我是義縣遊學電子科技.今天來跟大家說一個工作中常用到的操作,python3.8操作MariaDB資料庫.因為MariaDB屬於mysql分支因此資料庫命令語句都是通用的非常方便.= mysql.connector.connect(host="localhost", # 資料庫主機地址user="root", # 資料庫用戶名passwd="root", # 資料庫密碼database="test1" #資料庫名)mycursor = mydb.cursor()a=0while True:time.sleep(1)try:# 查詢資料庫的表格table1mycursor.execute
  • sqltoy-orm-4.16.11 發版,部分功能優化
    ,classType) 便於對分頁模型整體類型轉換sqltoy的十四個關鍵特點:1、最簡最直觀的sql編寫方式(不僅僅是查詢語句),採用條件參數前置處理規整法,讓sql語句部分跟客戶端保持高度一致2、sql中支持注釋(規避了對hint特性的影響,知道hint嗎?
  • mysql/mariadb資料庫在查詢結果中再次查詢篩選的操作方法
    今天是2020年4月9日,我跟大家分享一個二次操作mysql資料庫查詢結果的方法.我以資料庫 mariadb為例進行說明.因為它有個heidiSQL圖形管理工具,比較好操作.資料庫 mariadb為例進行說明.因為它有個heidiSQL圖形管理工具,比較好操作.
  • 基於MySQL資料庫應用開發實現嵌入式數控系統的設計
    因此作者採用批處理模式向表中填入數據的方法,具體作法是:首先創建一個存儲SQL語句的文本文件amend_data.sql,如圖1所示;然後執行SQL語句:% mysql –ulyw –p lyw00001 work 就將圖1中的數據加入到tbl_amend_amend表中了,如表1所示。
  • 如何向mysql導入數據
    一、導入.sql文件1.mysql命令導入數據基本語法:mysql -h伺服器地址 -u用戶名 -p 資料庫名< 要導入的資料庫文件路徑例:導入G:/mydb.sql2.source命令導入數據基本語法:source 資料庫文件路徑;例:導入G:/mydb2.sql
  • 將mysql數據導入access資料庫
    mysql資料庫表sqltable 欄位id,name,sex,email access資料庫表accesstable id,name,sex,email ?
  • 一千行MySQL學習筆記
    利用 mysqldump 指令完成。-- 導出1. 導出一張表  mysqldump -u用戶名 -p密碼 庫名 表名 > 文件名(D:/a.sql)2. 導出多張表  mysqldump -u用戶名 -p密碼 庫名 表1 表2 表3 > 文件名(D:/a.sql)3.
  • DTCC:MySQl核心代碼開發經驗揭示
    /mysql-test-run.pl --record sample  • 腳本mysql-test-run.pl會自動啟動mysqld,並將測試文件中的語句發送到mysqld執行,並比較結果  • 使用 --gdb 可以自動啟動調試器調試mysqld  $ .
  • 使用exp進行SQL報錯注入 - 51CTO.COM
    ---+1 row in set (0.00 sec)mysql> select exp(710);ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'在MySQL中,exp與ln和log的功能相反,簡單介紹下,就是log和ln都返回以e為底數的對數,見等式:
  • SQL是如何在資料庫中執行的?
    執行器負責解析 SQL 執行查詢存儲引擎負責保存數據。SQL是如何在執行器中執行的 ?我們通過一個例子來看一下,執行器是如何來解析執行一條 SQL 的。這個 SQL 語義是,查詢用戶 ID 大於 50 的用戶的所有訂單,這是很簡單的一個聯查,需要查詢 users 和 orders 兩張表,WHERE 條件就是,用戶 ID 大於 50。
  • 實踐分享:ACCESS資料庫導入mysql資料庫
    下面我就和大家分享一下如何把數據從ACCESS資料庫導入mysql資料庫。首先說明一下思路,可以導入mysql的方法有很多,但不包過access直接導入,網上有很多介紹CSV格式導入mysql的,但操作很不方便,經常發生錯誤而無法導入。sql語句導入是最簡單的。那有沒有辦法把access資料庫轉換成sql語句呢。
  • 根據經緯度查詢附近的xxx
    foreach ($keysvalue as $k=>$v){ $new_array[$k] = $arr[$k]; } return $new_array; } //當前坐標3000米範圍內所有商家$quares = returnSquarePoint($lng,$lat,3000);mysql_connect
  • 大數據分析工程師入門9-Spark SQL
    SparkSQL替代的是HIVE的查詢引擎,HIVE的默認引擎查詢效率低是由於其基於MapReduce實現SQL查詢,而MapReduce的shuffle是基於磁碟的。examples/src/main/resources/people.txt") .map(_.split(",")) .map(attributes => Person(attributes(0), attributes(1).trim.toInt)) .toDF()// 將DataFrame註冊成表peopleDF.createOrReplaceTempView("people") // 執行一條
  • 如何把Access的數據導入到Mysql中
    在建設網站的過程中,經常要處理一些數據的導入及導出。在Mysql資料庫中,一般有兩種方法來處理數據的導出:1. 使用select * from table_name into outfile 「file_name」; 2. 使用mysqldump實用程序 下面我們來舉例說明: 假設我們的資料庫中有一個庫為samp_db,一個表為samp_table。現在要把samp_table的數據導出。