【转】2.1【MySQL】运行原理(一):查询sql的执行过程及MySQL架构分析
MySQL的發展歷史和版本分支:
| 1996 年 | MySQL1.0 發布。它的歷史可以追溯到 1979 年,作者 Monty 用 BASIC 設計的一個報表工具。 |
| 1996 年 10 月 | 3.11.1 發布。MySQL 沒有 2.x 版本。 |
| 2000 年 | ISAM 升級成 MyISAM 引擎。MySQL 開源。 |
| 2003 年 | MySQL4.0 發布,集成 InnoDB 存儲引擎 |
| 2005 年 | MySQL5.0 版本發布,提供了視圖、存儲過程等功能。 |
| 2008 年 | MySQLAB 公司被 Sun 公司收購,進入 SunMySQL 時代。 |
| 2009 年 | Oracle 收購 Sun 公司,進入 OracleMySQL 時代。 |
| 2010 年 | MySQL5.5 發布,InnoDB 成為默認的存儲引擎。 |
| 2016 年 | MySQL 發布 8.0.0 版本。為什么沒有 6、7?5.6 可以當成 6.x,5.7 可以當 成 7.x。 |
因為MySQL是開源的(也有收費版本),所以在MySQL穩定版本的基礎上也發展出來了很多的分支,就像Linux一樣,有Ubuntu、RedHat、CentOS、 Fedora 、Debian等等。大家最熟悉的應該是MariaDB,因為CentOS7里面自帶了一個MariaDB。它是怎么來的呢?Oracle收購MySQL之后,MySQL創始人之一Monty擔心MySQL數據庫的未來(開發緩慢,封閉,可能會被閉源),就創建了一個分支MariaDB,默認使用全新的Maria存儲引擎,它是原MyISAM存儲引擎的升級版本。
其他流行分支:
- Percona Server 是MySQL重要的分支之一,它基于InnoDB存儲引擎的基礎上,提升了性能和易管理性,最后形成了增強版的XtraDB引擎,可以用來更好地發揮服務器硬件上的性能。
- 國內也有一些MySQL的分支或者自研的存儲引擎,比如網易的InnoSQL,極數云舟的ArkDB。
我們操作數據庫有各種各樣的方式,比如Linux 系統中的命令行,比如數據庫工具Navicat,比如程序,例如Java語言的JDBC API或者ORM框架。但大家有沒有思考過,當我們的工具或者程序連接到數據庫之后,實際上發生了什么事情?它的內部是怎么工作的?
下面以數據查詢為例,來看下MySQL的工作流程是什么樣的:
我們的程序或者工具要操作數據庫,第一步要做什么事情?跟數據庫建立連接。
1.與數據庫建立連接
首先,MySQL必須要運行一個服務,監聽默認的3306端口。在我們開發系統跟第三方對接的時候,必須要弄清楚的有兩件事。
- 第一個就是通信協議,比如我們是用TCP/UDP還是HTTP還是WebService?
- 第二個是消息格式,比如我們用XML格式,還是JSON格式,還是定長格式?報文頭長度多少,包含什么內容,每個字段的詳細含義?
1.1 通信類型和連接方式
MySQL是支持多種通信協議的,可以使用同步/異步的方式,支持長連接/短連接。這里我們拆分來看。
通信類型:同步或者異步
-
同步通信的特點:
- 同步通信依賴于被調用方,受限于被調用方的性能。也就是說,應用操作數據庫,線程會阻塞,等待數據庫的返回
- 一般只能做到一對一,很難做到一對多的通信
-
異步跟同步相反:
- 異步可以避免應用阻塞等待,但是不能節省SQL執行的時間
- 如果異步存在并發,每一個SQL的執行都要單獨建立一個連接,避免數據混亂。但是這樣會給服務端帶來巨大的壓力(一個連接就會創建一個線程,線程間切換會占用大量CPU資源)。另外異步通信還帶來了編碼的復雜度,所以一般不建議使用。如果要異步,必須使用連接池,排隊從連接池獲取連接而不是創建新連接
一般來說我們連接數據庫都是同步連接。
連接方式:長連接或者短連接
MySQL既支持短連接,也支持長連接。短連接就是操作完畢以后,馬上close 掉。長連接可以保持打開,減少服務端創建和釋放連接的消耗,后面的程序訪問的時候還可以使用這個連接。一般我們會在連接池中使用長連接。
保持長連接會消耗內存。長時間不活動的連接,MySQL服務器會斷開。那這個超時時間怎么查看呢?
show global variables like 'wait_timeout'; -- 非交互式超時時間,如 JDBC 程序 show global variables like' interactive_timeout'; -- 交互式超時時間,如數據庫工具執行結果如下圖。默認都是28800秒,8小時
我們怎么查看MySQL當前有多少個連接?可以用show status命令:
show global status like 'Thread%';- Threads_cached:緩存中的線程連接數
- Threads_connected:當前打開的連接數
- Threads_created:為處理連接創建的線程數
- Threads_running:非睡眠狀態的連接數,通常指并發連接數
有了連接數,怎么知道當前連接的狀態?可以使用show processlist命令,(root用戶)查看SQL的執行狀態。
SHOW PROCESSLIST;從上面的Threads_connected可以看到當前有4個連接,所以這里的顯示了4個連接狀態。那Command這一列是什么意思呢?一些常見的狀態:
| Sleep | 線程正在等待客戶端,以向它發送一個新語句 |
| Query | 線程正在執行查詢或往客戶端發送數據 |
| Locked | 該查詢被其它查詢鎖定 |
| Copying to tmp tableondisk | 臨時結果集合大于 tmp_table_size。線程把臨時表從存儲器內部格式改 變為磁盤模式,以節約存儲器 |
| Sendingdata | 線程正在為 SELECT 語句處理行,同時正在向客戶端發送數據 |
| Sortingforgroup | 線程正在進行分類,以滿足 GROUPBY 要求 |
| Sortingfororder | 線程正在進行分類,以滿足 ORDERBY 要求 |
還有個問題,MySQL服務允許的最大連接數是多少呢?
show variables like 'max_connections';在5.7版本中默認是151個,最大可以設置成16384(2^14)。
set global max_connections=1000;1.2 MySQL支持哪些通信協議
第一種是Unix Socket。比如我們在Linux服務器上,如果沒有指定-h參數,它就用socket方式登錄。如下圖(省略
了-S /var/lib/mysql/mysql.sock)
它不用通過網絡協議,也可以連接到MySQL的服務器,它需要用到服務器上的一個物理文件(/var/lib/mysql/mysql.sock)
select @@socket;如果有參數-h指定主機,就會用第二種方式,TCP/IP協議。
mysql -h192.168.8.211 -uroot -p123456我們的編程語言的連接模塊都是用 TCP 協議連接到 MySQL 服務器的,比如 mysql-connector-java-x.x.xx.jar。
1.3 MySQL采用什么通信方式
通信方式分為以下三種:
- 單工:在兩臺計算機通信的時候,數據的傳輸是單向的。比如遙控器
- 半雙工:在兩臺計算機之間,數據傳輸是雙向的,你可以給我發送,我也可以給你發送,但是在這個通訊連接里面,同一時間只能有一臺服務器在發送數據,也就是你要給我發的話,也必須等我發給你完了之后才能給我發。比如對講機
- 全雙工:數據的傳輸是雙向的,并且可以同時傳輸。比如打電話
那MySQL應該采用哪種通信方式呢?半雙工的通信方式,因為客戶端與服務端肯定是雙向通信的,而且要么是客戶端向服務端發送數據,要么是服務端向客戶端發送數據,這兩個動作不能同時發生。
客戶端發送SQL語句給服務端的時候,(在一次連接里面)數據是不能分成小塊發送的,不管你的SQL語句有多大,都是一次性發送。比如我們用MyBatis動態SQL生成了一個批量插入的語句, 插入10萬條數據, values后面跟了一長串的內容,或者where條件in里面的值太多,會出現問題。這個時候我們必須要調整MySQL服務器配置 max_allowed_packet 參數的值(默認是4M),把它調大,否則就會報錯。
另一方面,對于服務端來說,也是一次性發送所有的數據,不能因為你已經取到了想要的數據就中斷操作,這個時候會對網絡和內存產生大量消耗。所以,我們一定要在程序里面避免不帶limit 的這種操作,比如一次把所有滿足條件的數據全部查出來,一定要先count一下。如果數據量大的話,可以分批查詢。
執行一條查詢語句,客戶端跟服務端建立連接之后呢?下一步要做什么?
2.查詢緩存
MySQL內部自帶了一個緩存模塊。緩存的作用我們應該很清楚了,把數據以KV的形式放到內存里面,可以加快數據的讀取速度,也可以減少服務器處理的時間。但是MySQL的緩存我們好像比較陌生,從來沒有去配置過,也不知道它什么時候生效?假如 t_user 表有500萬行數據,沒有索引。我們在沒有索引的字段上執行同樣的查詢,大家覺得第二次會快嗎?
可以看到MySQL緩存是默認關閉的。默認關閉的意思就是不推薦使用,為什么MySQL不推薦使用它自帶的緩存呢?
主要是因為MySQL自帶的緩存的應用場景有限,需要滿足以下兩個要求:
- 它要求SQL語句必須一模一樣,中間多一個空格,字母大小寫不同都被認為是不同的的SQL。
- 表里面任何一條數據發生變化的時候,這張表所有緩存都會失效,所以對于有大量數據更新的應用,也不適合。
所以緩存這一塊,我們還是交給ORM框架(比如MyBatis默認開啟了一級緩存),或者獨立的緩存服務,比如Redis來處理更合適。在MySQL 8.0中,查詢緩存已經被移除了。
我們沒有使用緩存的話,就會跳過緩存的模塊,下一步我們要做什么呢?
3. 語法解析和預處理
這里我會有一個疑問,為什么我的一條SQL語句能夠被識別呢?假如我隨便執行一個字符串penyuyan,服務器報了一個1064的錯:
它是怎么知道我輸入的內容是錯誤的?這個就是MySQL的Parser解析器和Preprocessor預處理模塊。這一步主要做的事情是對語句基于SQL語法進行詞法和語法分析和語義的解析。
3.1 詞法分析
詞法分析就是把一個完整的SQL語句打碎成一個個的單詞。比如一個簡單的SQL語句:
select username from t_user where id = 1;它會打碎成8個符號,每個符號是什么類型,從哪里開始到哪里結束。
3.2 語法分析
第二步就是語法分析,語法分析會對SQL做一些語法檢查,比如單引號有沒有閉合,然后根據MySQL定義的語法規則,根據SQL語句生成一個數據結構。這個數據結構我們把它叫做解析樹(select_lex)。
任何數據庫的中間件,比如 Mycat,Sharding-JDBC(用到了Druid Parser),都必須要有詞法和語法分析功能,在市面上也有很多的開源的詞法解析的工具(比如LEX,Yacc)。
3.3 預處理器
問題:如果我寫了一個詞法和語法都正確的SQL,但是表名或者字段不存在,會在哪里報錯?是在數據庫的執行層還是解析器?比如:
select * from penyuyan;解析器可以分析語法,但是它怎么知道數據庫里面有什么表,表里面有什么字段呢?實際上還是在解析的時候報錯,解析SQL的環節里面有個預處理器。它會檢查生成的解析樹,解決解析器無法解析的語義。比如,它會檢查表和列名是否存在,檢查名字和別名,保證沒有歧義。預處理之后得到一個新的解析樹。
4.查詢優化得到執行計劃
得到解析樹之后,是不是執行SQL語句了呢?這里我們有一個問題,一條SQL語句是不是只有一種執行方式?或者說數據庫最終執行的SQL是不是就是我們發送的SQL?
這個答案是否定的。一條SQL語句是可以有很多種執行方式的,最終返回相同的結果,他們是等價的。但是如果有這么多種執行方式,這些執行方式怎么得到的?最終選擇哪一種去執行?根據什么判斷標準去選擇?
這個就是MySQL的查詢優化器的模塊(Query Optimizer)。
4.1 什么是優化器?
查詢優化器的目的就是根據解析樹生成不同的執行計劃(ExecutionPlan),然后選擇一種最優的執行計劃。MySQL里面使用的是基于開銷(cost)的優化器,哪種執行計劃開銷最小,就用哪種。可以使用這個命令查看查詢的開銷:
show status like 'Last_query_cost';mysql官網關于這里的參考,想了解更多的同學可以看看
4.2 優化器可以做什么?
MySQL的優化器能處理哪些優化類型呢?舉兩個簡單的例子:
實際上,對于每一種數據庫來說,優化器的模塊都是必不可少的,他們通過復雜的算法實現盡可能優化查詢效率的目標。如果對于優化器的細節感興趣,可以看看《數據庫查詢優化器的藝術-原理解析與SQL性能優化》。
但是優化器也不是萬能的,并不是再垃圾的SQL語句都能自動優化,也不是每次都能選擇到最優的執行計劃,大家在編寫SQL語句的時候還是要注意。
如果我們想知道優化器是怎么工作的,它生成了幾種執行計劃,每種執行計劃的cost是多少,應該怎么做?
4.3 優化器得到執行計劃的過程?
首先,我們要啟用優化器的追蹤(默認是關閉的):
SHOW VARIABLES LIKE 'optimizer_trace'; set optimizer_trace = 'enabled=on';注意,開啟這開關是會消耗性能的,因為它要把優化分析的結果寫到表里面,所以不要輕易開啟,或者查看完之后關閉它(改成off)。接著我們執行一個SQL語句,優化器會生成執行計劃(下面是一個兩表聯查):
SELECT d.username,i.phone from user_info i,user_detail d WHERE i.id=d.user_id;這個時候優化器分析的過程已經記錄到系統表里面了,我們可以查詢
select * from information_schema.optimizer_trace\G;如果是直接在Navicat中執行,那么得到結果只是短短一行數據,并沒有完整的執行計劃
所以,此處應該是在命令行中執行:
得到的優化器分析的過程是一個JSON類型的數據,主要分成三部分,準備階段、優化階段和執行階段。
那具體的優化計劃在哪呢?在優化階段(“join_optimization”)中的 considered_execution_plans
最后,分析完記得關掉它:
set optimizer_trace="enabled=off"; SHOWVARIABLESLIKE'optimizer_trace';優化完之后,得到一個什么東西呢?
4.4 如何查看最終執行計劃?
優化器最終會把解析樹變成一個查詢執行計劃,查詢執行計劃是一個數據結構。
當然,這個執行計劃是不是一定是最優的執行計劃呢?不一定,因為MySQL也有可能覆蓋不到所有的執行計劃。我們怎么查看MySQL的執行計劃呢?比如多張表關聯查詢,先查詢哪張表?在執行查詢的時候可能用到哪些索引,實際上用到了什么索引?
MySQL提供了一個執行計劃的工具。我們在SQL語句前面加上EXPLAIN,就可以看到執行計劃的信息。
5.存儲引擎,存儲數據
得到執行計劃以后,SQL語句是不是終于可以執行了?問題又來了:
5.1 存儲引擎基本介紹
我們先回答第一個問題:在關系型數據庫里面,數據是放在什么結構里面的?放在表Table里面的,我們可以把這個表理解成Excel電子表格的形式。所以我們的表在存儲數據的同時,還要組織數據的存儲結構,這個存儲結構就是由我們的存儲引擎決定的,所以我們也可以把存儲引擎叫做表類型。
在MySQL里面,支持多種存儲引擎,他們是可以替換的,所以叫做插件式的存儲引擎。為什么要搞這么多存儲引擎呢?一種還不夠用嗎?這個問題先留著。
5.2 查看存儲引擎
比如我們數據庫里面已經存在的表,我們怎么查看它們的存儲引擎呢?
show table status from `forum`; --forum是指定數據庫名另外,還通過DDL建表語句來查看。
在MySQL里面,我們創建的每一張表都可以指定它的存儲引擎,而不是一個數據庫只能使用一個存儲引擎。存儲引擎的使用是以表為單位的。而且,創建表之后還可以修改存儲引擎。
我們說一張表使用的存儲引擎決定我們存儲數據的結構,那在服務器上它們是怎么存儲的呢?我們先要找到數據庫存放數據的路徑:
show variables like 'datadir';默認情況下,每個數據庫有一個自己文件夾,以forum數據庫為例。
任何一個存儲引擎都有一個frm文件,這個是表結構定義文件。不同的存儲引擎存放數據的方式不一樣,產生的文件也不一樣,innodb 是 1 個,memory沒有,myisam是兩個。
這些存儲引擎的差別在哪呢?
5.3 存儲引擎比較
MyISAM 和InnoDB 是我們用得最多的兩個存儲引擎,在 MySQL 5.5 版本之前,默認的存儲引擎是MyISAM,它是MySQL自帶的。我們創建表的時候不指定存儲引擎,它就會使用MyISAM作為存儲引擎。MyISAM的前身是ISAM(IndexedSequentialAccessMethod:利用索引,順序存取數據的方法)。
5.5版本之后默認的存儲引擎改成了InnoDB,它是第三方公司為MySQL開發的。為什么要改呢?最主要的原因還是InnoDB 支持事務,支持行級別的鎖,對于業務一致性要求高的場景來說更適合。
這個里面又有Oracle和MySQL公司的一段恩怨情仇。
InnoDB本來是InnobaseOy公司開發的, 它和MySQLAB公司合作開源了InnoDB的代碼。但是沒想到MySQL的競爭對手Oracle把InnobaseOy收購了。后來08年Sun公司(開發Java語言的Sun)收購了MySQLAB,09年Sun公司又被 Oracle 收購了,所以 MySQL,InnoDB 又是一家了。有人覺得 MySQL 越來越像Oracle,其實也是這個原因。
那么除了這兩個我們最熟悉的存儲引擎,數據庫還支持其他哪些常用的存儲引擎呢?我們可以用這個命令查看數據庫對存儲引擎的支持情況:
show engines;其中有存儲引擎的描述和對事務、XA協議和Savepoints的支持。
- XA協議用來實現分布式事務(分為本地資源管理器,事務管理器)。
- Savepoints用來實現子事務(嵌套事務)。創建了一個Savepoints之后,事務就可以回滾到這個點,不會影響到創建Savepoints之前的操作。
這些數據庫支持的存儲引擎,分別有什么特性呢?
-
MyISAM(3 個文件)
應用范圍比較小。表級鎖定限制了讀/寫的性能,因此在Web 和數據倉庫配置中,它通常用于只讀或以讀為主的工作。
-
支持表級別的鎖(插入和更新會鎖表)。不支持事務
-
擁有較高的插入(insert)和查詢(select)速度
-
存儲了表的行數(count速度更快)。(怎么快速向數據庫插入100萬條數據?我們有一種先用MyISAM插入數據,然后修改存儲引擎為InnoDB的操作)
-
適用:只讀之類的數據分析的項目
-
-
InnoDB(2 個文件)
mysql5.7中的默認存儲引擎。 InnoDB是一個事務安全(與ACID兼容)的MySQL存儲引擎,它具有提交、回滾和崩潰恢復功能來保護用戶數據。InnoDB行級鎖(不升級為更粗粒度的鎖)和Oracle風格的一致非鎖讀提高了多用戶并發性和性能。InnoDB將用戶數據存儲在聚集索引中,以減少基于主鍵的常見查詢的I/O。為了保持數據完整性,InnoDB還支持外鍵引用完整性約束。
-
支持事務,支持外鍵,因此數據的完整性、一致性更高
-
支持行級別的鎖和表級別的鎖
-
支持讀寫并發,寫不阻塞讀(MVCC)
-
特殊的索引存放方式,可以減少IO,提升查詢效率
-
適用:經常更新的表,存在并發讀寫或者有事務處理的業務系統
-
-
Memory(1 個文件)
將所有數據存儲在RAM中,以便在需要快速查找非關鍵數據的環境中快速訪問。這個引擎以前被稱為堆引擎。其使用案例正在減少; InnoDB及其緩沖池內存區域提供了一種通用、持久的方法來將大部分或所有數據保存在內存中,而ndbcluster為大型分布式數據集提供了快速的鍵值查找。
- 把數據放在內存里面,讀寫的速度很快,但是數據庫重啟或者崩潰,數據會全部消失。只適合做臨時表。
- 將表中的數據存儲到內存中
-
CSV(3 個文件)
它的表實際上是帶有逗號分隔值的文本文件。 csv表允許以csv格式導入或轉儲數據,以便與讀寫相同格式的腳本和應用程序交換數據。因為csv 表沒有索引,所以通常在正常操作期間將數據保存在innodb表中,并且只在導入或導出階段使用csv表。
不允許空行,不支持索引。格式通用,可以直接編輯,適合在不同數據庫之間導入導出。
-
Archive(2 個文件)
這些緊湊的未索引的表用于存儲和檢索大量很少引用的歷史、存檔或安全審計信息。
不支持索引,不支持update delete.
這是MySQL里面常見的一些存儲引擎,我們看到了,不同的存儲引擎提供的特性都不一樣,它們有不同的存儲機制、索引方式、鎖定水平等功能。我們在不同的業務場景中對數據操作的要求不同,就可以選擇不同的存儲引擎來滿足我們的需求,這個就是MySQL支持這么多存儲引擎的原因。
5.4 如何選擇存儲引擎?
- 如果對數據一致性要求比較高,需要事務支持,可以選擇InnoDB。
- 如果數據查詢多更新少,對查詢性能要求比較高,可以選擇MyISAM。
- 如果需要一個用于查詢的臨時表,可以選擇Memory。
- 如果所有的存儲引擎都不能滿足你的需求,并且技術能力足夠,可以根據官網內部手冊用C語言開發一個存儲引擎
6.執行引擎,返回結果
OK,存儲引擎分析完了,它是我們存儲數據的形式,繼續第二個問題,是誰使用執行計劃去操作存儲引擎呢?這就是我們的執行引擎,它利用存儲引擎提供的相應的API來完成操作。為什么我們修改了表的存儲引擎,操作方式不需要做任何改變?因為不同功能的存儲引擎實現的API是相同的。
最后把數據返回給客戶端,即使沒有結果也要返回。
==> MySQL架構
基于上面分析的流程,我們一起來梳理一下MySQL的內部模塊。
1.模塊詳解
2.架構分層
總體上,我們可以把MySQL分成三層,跟客戶端對接的連接層,真正執行操作的服務層,和跟硬件打交道的存儲引擎層(參考MyBatis:接口、核心、基礎)。
連接層
我們的客戶端要連接到MySQL服務器3306端口,必須要跟服務端建立連接,那么管理所有的連接,驗證客戶端的身份和權限,這些功能就在連接層完成。
服務層
連接層會把SQL語句交給服務層,這里面又包含一系列的流程:比如查詢緩存的判斷、根據SQL調用相應的接口,對我們的SQL語句進行詞法和語法的解析(比如關鍵字怎么識別,別名怎么識別,語法有沒有錯誤等等)。
然后就是優化器,MySQL底層會根據一定的規則對我們的 SQL語句進行優化,最后再交給執行器去執行。
存儲引擎
存儲引擎就是我們的數據真正存放的地方,在MySQL里面支持不同的存儲引擎。再往下就是內存或者磁盤。
總結
以上是生活随笔為你收集整理的【转】2.1【MySQL】运行原理(一):查询sql的执行过程及MySQL架构分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三星堆发现裙立发人像:大背头、粗腰带、或
- 下一篇: 【转】深入浅出OOP(六): 理解C#的