MySQL執行SQL的流程


1.一條SQL語句的執行流程

1.1通信協議

MySQL 是支持多種通信協議的,可以使用同步/異步的方式,支持長連接/短連接。
# 通信類型:同步或者異步
同步通信的特點:
1、同步通信依賴於被調用方,受限於被調用方的性能。也就是說,應用操作數據庫,線程會阻塞,等待數據庫的返回。
2、一般只能做到一對一,很難做到一對多的通信。

異步跟同步相反:
1、異步可以避免應用阻塞等待,但是不能節省 SQL 執行的時間。
2、如果異步存在並發,每一個 SQL 的執行都要單獨建立一個連接,避免數據混亂。但是這樣會給服務端帶來巨大的壓力
(一個連接就會創建一個線程,線程間切換會占用大量 CPU 資源)。另外異步通信還帶來了編碼的復雜度,所以一般不建議使用。
如果要異步,必須使用連接池,排隊從連接池獲取連接而不是創建新連接。
一般來說我們連接數據庫都是同步連接。

# 連接方式:長連接或者短連接
MySQL 既支持短連接,也支持長連接。
保持長連接會消耗內存。長時間不活動的連接,MySQL 服務器會斷開。
show global variables like 'wait_timeout'; -- 非交互式超時時間,如 JDBC 程序
show global variables like 'interactive_timeout'; -- 交互式超時時間,如數據庫工具
-- 默認都是 28800 秒,8 小時。

查看 MySQL 當前有多少個連接?

show global status like 'Thread%';
Threads_cached:緩存中的線程連接數。
Threads_connected:當前打開的連接數。
Threads_created:為處理連接創建的線程數。
Threads_running:非睡眠狀態的連接數,通常指並發連接數。

查詢當前連接的狀態?

SHOW PROCESSLIST;
-- https://dev.mysql.com/doc/refman/5.7/en/show-processlist.html
常見的狀態:
https://dev.mysql.com/doc/refman/5.7/en/thread-commands.html
狀態 含義
Sleep 線程正在等待客戶端,以向它發送一個新語句
Query 線程正在執行查詢或往客戶端發送數據
Locked 該查詢被其它查詢鎖定
Copying to tmptable on disk 臨時結果集合大於 tmp_table_size。線程把臨時表從存儲器內部格式改變為磁盤模式,以節約存儲器
Sending data 線程正在為 SELECT 語句處理行,同時正在向客戶端發送數據
Sorting for group 線程正在進行分類,以滿足 GROUP BY 要求
Sorting for order 線程正在進行分類,以滿足 ORDER BY 要求

MySQL 服務允許的最大連接數是多少呢?
在 5.7 版本中默認是 151 個,最大可以設置成 16384(2^14).

show variables like 'max_connections';
show 的參數說明:
1、級別:會話 session 級別(默認);全局 global 級別
2、動態修改:set,重啟后失效;永久生效,修改配置文件/etc/my.cnf
# MySQL 支持哪些通信協議?
第一種是 Unix Socket。
比如我們在 Linux 服務器上,如果沒有指定-h 參數,它就用 socket 方式登錄。
如果指定-h 參數,就會用第二種方式,TCP/IP 協議。
命名管道(Named Pipes)和內存共享(Share Memory)的方式,這兩種
通信方式只能在 Windows 上面使用,一般用得比較少。

1.2通信方式

MySQL 使用了半雙工的通信方式?
  要么是客戶端向服務端發送數據,要么是服務端向客戶端發送數據,這兩個動作不能同時發生。所以客戶端發送 SQL 語句給服務端的時候,
(在一次連接里面)數據是不能分成小塊發送的,不管你的 SQL 語句有多大,都是一次性發送。比如我們用MyBatis動態SQL生成了一個批量插入的語句,
插入10萬條數據,values后面跟了一長串的內容,或者 where 條件 in 里面的值太多,會出現問題。這個時候我們必須要調整 MySQL 服務器配置 
max_allowed_packet 參數的值(默認是 4M),把它調大,否則就會報錯。
  另一方面,對於服務端來說,也是一次性發送所有的數據,不能因為你已經取到了想要的數據就中斷操作,
這個時候會對網絡和內存產生大量消耗。所以,我們一定要在程序里面避免不帶 limit 的這種操作,比如一次把所有滿足條件的數據全部查出來,
一定要先 count 一下。如果數據量的話,可以分批查詢。

1.3查詢緩存

show variables like 'query_cache%';
# 默認關閉的意思就是不推薦使用,為什么 MySQL 不推薦使用它自帶的緩存呢?
   主要是因為 MySQL 自帶的緩存的應用場景有限,第一個是它要求 SQL 語句必須一模一樣,中間多一個空格,字母大小寫不同都被認為是不同的的 SQL。
   第二個是表里面任何一條數據發生變化的時候,這張表所有緩存都會失效,所以對於有大量數據更新的應用,也不適合。
   所以緩存這一塊,我們還是交給 ORM 框架(比如 MyBatis 默認開啟了一級緩存),或者獨立的緩存服務,比如 Redis 來處理更合適。
   在 MySQL 8.0 中,查詢緩存已經被移除了。

1.4 語法 解析和預處理(Parser & Preprocessor)

使用 MySQL 的 Parser 解析器和 Preprocessor 預處理模塊

1.4.1 詞法解析

詞法分析就是把一個完整的 SQL 語句打碎成一個個的單詞。
比如一個簡單的 SQL 語句:

select name from user where id = 1;

它會打碎成 8 個符號,每個符號是什么類型,從哪里開始到哪里結束。

1.4.2 語法解析

第二步就是語法分析,語法分析會對 SQL 做一些語法檢查,比如單引號有沒有閉合,然后根據 MySQL 定義的語法規則,根據 SQL 語句生成一個數據結構。這個數據結構我
們把它叫做解析樹(select_lex)

任何數據庫的中間件,比如 Mycat,Sharding-JDBC(用到了 Druid Parser),都必須要有詞法和語法分析功能,在市面上也有很多的開源的詞法解析的工具(比如 LEX-Yacc)。

1.4.3 預處理器

問題:如果我寫了一個詞法和語法都正確的 SQL,但是表名或者字段不存在,會在哪里報錯?是在數據庫的執行層還是解析器?比如:

select * from user;

解析器可以分析語法,但是它怎么知道數據庫里面有什么表,表里面有什么字段呢?
實際上還是在解析的時候報錯,解析 SQL 的環節里面有個預處理器。它會檢查生成的解析樹,解決解析器無法解析的語義。比如,它會檢查表和列名是
否存在,檢查名字和別名,保證沒有歧義。預處理之后得到一個新的解析樹。

1.5 查詢優化(Query Optimizer)與查詢執行計划

1.5.1 什么是優化器?

得到解析樹之后,是不是執行 SQL 語句了呢?一條 SQL 語句是不是只有一種執行方式?或者說數據庫最終執行的SQL是不是就是我們發送的 SQL?
這個答案是否定的。一條 SQL 語句是可以有很多種執行方式的,最終返回相同的結果,他們是等價的。但是如果有這么多種執行方式,
這些執行方式怎么得到的?最終選擇哪一種去執行?根據什么判斷標准去選擇?
這個就是 MySQL 的查詢優化器的模塊(Optimizer)。查詢優化器的目的就是根據解析樹生成不同的執行計划(Execution Plan),
然后選擇一種最優的執行計划,MySQL 里面使用的是基於開銷(cost)的優化器,那種執行計划開銷最小,就用哪種。

查看查詢開銷的命令:

show status like 'Last_query_cost';
-- https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Last_query_cost

1.5.2 優化器可以做什么?

1、當我們對多張表進行關聯查詢的時候,以哪個表的數據作為基准表。
2、有多個索引可以使用的時候,選擇哪個索引。實際上,對於每一種數據庫來說,優化器的模塊都是必不可少的,他們通過復雜的算法實現盡可能優化查詢效率的目標。

1.5.3 優化器是怎么得到執行計划的?

https://dev.mysql.com/doc/internals/en/optimizer-tracing.html
首先我們要啟用優化器的追蹤(默認是關閉的):

SHOW VARIABLES LIKE 'optimizer_trace';
set optimizer_trace='enabled=on';

注意開啟這開關是會消耗性能的,因為它要把優化分析的結果寫到表里面,所以不要輕易開啟,或者查看完之后關閉它(改成 off)。
注意:參數分為 session 和 global 級別。
接着我們執行一個 SQL 語句,優化器會生成執行計划:

select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;

這個時候優化器分析的過程已經記錄到系統表里面了,我們可以查詢:

select * from information_schema.optimizer_trace\G

它是一個 JSON 類型的數據,主要分成三部分,准備階段、優化階段和執行階段。

expanded_query 是優化后的 SQL 語句。
considered_execution_plans 里面列出了所有的執行計划。
分析完記得關掉它:

set optimizer_trace="enabled=off";
SHOW VARIABLES LIKE 'optimizer_trace';

1.5.4 優化器得到的結果

優化完之后,得到一個什么東西呢?
優化器最終會把解析樹變成一個查詢執行計划,查詢執行計划是一個數據結構。當然,這個執行計划是不是一定是最優的執行計划呢?不一定,因為 MySQL 也有可能覆蓋不到所有的執行計划。
我們怎么查看 MySQL 的執行計划呢?比如多張表關聯查詢,先查詢哪張表?在執行查詢的時候可能用到哪些索引,實際上用到了什么索引?
MySQL 提供了一個執行計划的工具。我們在 SQL 語句前面加上 EXPLAIN,就可以看到執行計划的信息。
EXPLAIN select name from user where id=1;

*注意 Explain 的結果也不一定最終執行的方式

1.6存儲引擎

在關系型數據庫里面,數據是放在Table表里面的。我們可以把這個表理解成 Excel 電子表格的形式。所以我們的表在存儲數據的同時,
還要組織數據的存儲結構,這個存儲結構就是由我們的存儲引擎決定的,所以我們也可以把存儲引擎叫做表類型。
在 MySQL 里面,支持多種存儲引擎,他們是可以替換的,所以叫做插件式的存儲引擎。

1.6.1 查看存儲引擎

show table status from `user`;

或者通過 DDL 建表語句來查看。
在MySQL里面,我們創建的每一張表都可以指定它的存儲引擎,而不是一個數據庫只能使用一個存儲引擎。
存儲引擎的使用是以表為單位的。而且,創建表之后還可以修改存儲引擎。

數據庫存放數據的路徑:

show variables like 'datadir';

在MySQL 8.0之前任何一個存儲引擎都有一個frm文件,這個是表結構定義文件。

但MySQL8.0之后數據庫的innodb表全部放至在datadir下的mysql.ibd中;將不再把表結構放在.frm文件中,而是存放在元數據表中。
更多特性官網地址:https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html

1.6.2 存儲引擎的比較

MyISAM 和 InnoDB 是我們用得最多的兩個存儲引擎,在 MySQL 5.5 版本之前,默認的存儲引擎是 MyISAM,它是 MySQL 自帶的。
我們創建表的時候不指定存儲引擎,它就會使用 MyISAM 作為存儲引擎。MyISAM 的前身是 ISAM(Indexed Sequential Access 
Method:利用索引,順序存取數據的方法)。
5.5 版本之后默認的存儲引擎改成了 InnoDB,它是第三方公司為 MySQL 開發的。
為什么要改呢?
最主要的原因還是 InnoDB 支持事務,支持行級別的鎖,對於業務一致性要求高的場景來說更適合。

數據庫支持的存儲引擎:
我們可以用這個命令查看數據庫對存儲引擎的支持情況:

show engines;

其中有存儲引擎的描述和對事務、XA 協議和 Savepoints 的支持。XA 協議用來實現分布式事務(分為本地資源管理器,事務管理器)。
Savepoints 用來實現子事務(嵌套事務)。創建了一個 Savepoints之后,事務就可以回滾到這個點,不會影響到創建 Savepoints之前的操作。

數據庫支持的存儲引擎的特性:
https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html

MyISAM

應用范圍比較小。表級鎖定限制了讀/寫的性能,因此在 Web 和數據倉庫配置中,它通常用於只讀或以讀為主的工作。
特點:
   支持表級別的鎖(插入和更新會鎖表)。不支持事務。
   擁有較高的插入(insert)和查詢(select)速度。
   存儲了表的行數(count 速度更快)。
(怎么快速向數據庫插入 100 萬條數據?我們有一種先用 MyISAM 插入數據,然后修改存儲引擎為 InnoDB 的操作。)
適合:只讀之類的數據分析的項目。

InnoDB

mysql 5.7+和8.0 中的默認存儲引擎。InnoDB 是一個事務安全(與 ACID 兼容)的 MySQL存儲引擎,它具有提交、回滾和
崩潰恢復功能來保護用戶數據。InnoDB 行級鎖(不升級為更粗粒度的鎖)和 Oracle 風格的一致非鎖讀提高了多用戶並發性
和性能。InnoDB 將用戶數據存儲在聚集索引中,以減少基於主鍵的常見查詢的 I/O。為了保持數據完整性,InnoDB 還支持
外鍵引用完整性約束。
特點:
   支持事務,支持外鍵,因此數據的完整性、一致性更高。
   支持行級別的鎖和表級別的鎖。
   支持讀寫並發,寫不阻塞讀(MVCC)。
   特殊的索引存放方式,可以減少 IO,提升查詢效率。
適合:經常更新的表,存在並發讀寫或者有事務處理的業務系統。

Memory

將所有數據存儲在 RAM 中,以便在需要快速查找非關鍵數據的環境中快速訪問。這個引擎以前被稱為堆引擎。
其使用案例正在減少;InnoDB 及其緩沖池內存區域提供了一種通用、持久的方法來將大部分或所有數據保存在內存中,
而 ndbcluster 為大型分布式數據集提供了快速的鍵值查找。
特點:
把數據放在內存里面,讀寫的速度很快,但是數據庫重啟或者崩潰,數據會全部消失。只適合做臨時表。
將表中的數據存儲到內存中

CSV

它的表實際上是帶有逗號分隔值的文本文件。csv表允許以csv格式導入或轉儲數據,以便與讀寫相同格式的腳本和應用程序交換數據。
因為 csv 表沒有索引,所以通常在正常操作期間將數據保存在 innodb 表中,並且只在導入或導出階段使用 csv 表。
特點:
   不允許空行,不支持索引。格式通用,可以直接編輯,適合在不同數據庫之間導入導出。

Archive

這些緊湊的未索引的表用於存儲和檢索大量很少引用的歷史、存檔或安全審計信息。
特點:不支持索引,不支持 update delete。

1.6.3 存儲引擎的選擇

如果對數據一致性要求比較高,需要事務支持,可以選擇 InnoDB。
如果數據查詢多更新少,對查詢性能要求比較高,可以選擇 MyISAM。
如果需要一個用於查詢的臨時表,可以選擇 Memory。
如果所有的存儲引擎都不能滿足你的需求,並且技術能力足夠,可以根據官網內部手冊用 C 語言開發一個存儲引擎:
https://dev.mysql.com/doc/internals/en/custom-engine.html

1.7 執行引擎,返回結果

執行引擎,它利用存儲引擎提供的相應的 API 來完成操作,不同功能的存儲引擎實現的 API 是相同的。
最后把數據返回給客戶端,即使沒有結果也要返回。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM