第04章 逻辑架构【1.MySQL架构篇】【MySQL高级】
第04章 邏輯架構
- 前言
- 推薦
- 第04章 邏輯架構
- 1. 邏輯架構剖析
- 1.1 服務器處理客戶端請求
- 1.2 Connectors
- 1.3 第1層:連接層
- 1.4 第2層:服務層
- 1.5 第3層:引擎層
- 1.6 存儲層
- 1.7 小結
- 2. SQL執行流程
- 2.1 MySQL 中的 SQL執行流程
- 2.2 MySQL8中SQL執行原理
- 1.確認profiling 是否開啟
- 2.多次執行相同SQL查詢
- 3.查看profiles
- 4.查看profile
- 2.3 MySQL5.7中SQL執行原理
- 1.配置文件中開啟查詢緩存
- 2.重啟mysql服務
- 3.開啟查詢執行計劃
- 4.執行語句兩次:
- 5.查看profiles
- 6.查看profile
- 2.4 SQL語法順序
- 2.5 Oracle中的SQL執行流程(了解)
- 3. 數據庫緩沖池(buffer pool)
- 3.1 緩沖池 vs 查詢緩存
- 1. 緩沖池(Buffer Pool)
- 2. 查詢緩存
- 3.2 緩沖池如何讀取數據
- 3.3 查看/設置緩沖池的大小
- 3.4 多個Buffer Pool實例
- 3.5 引申問題
- 最后
前言
2022/7/30 11:21
暑假學習ing
推薦
【MySQL數據庫教程天花板,mysql安裝到mysql高級,強!硬!-嗶哩嗶哩】
尚硅谷MySQL學習筆記
Mysql筆記:第04章_邏輯架構
第04章 邏輯架構
1. 邏輯架構剖析
1.1 服務器處理客戶端請求
首先MySQL是典型的C/S架構,即Client/Server 架構,服務器端程序使用的mysqld.
不論客戶端進程和服務器進程是采用哪種方式進行通信,最后實現的效果都是:客戶端進程向服務器進程發送一段文本(SQL語句),服務器進程處理后再向客戶端進程發送一段文本(處理結果)。
那服務器進程對客戶端進程發送的請求做了什么處理,才能產生最后的處理結果呢?這里以查詢請求為例展示:
下面具體展開看一下(數字標號展示了5.7查詢的順序):
8.0中把查詢緩存刪掉了
————————————————
Connectors: MySQL服務器之外的客戶端程序,和具體編程語言相關的內容
Management Service &Utilities–>基礎服務組件: MySQL服務器的基礎服務組件
Connection Pool -->連接池: 提供了多個用于客戶端和服務器端進行交互的線程,這些線程使用完后交還到連接池,供其他客戶端使用,從而保證資源不被浪費
SQL Interface–>SQL接口: 作用是用來接收SQL指令并返回查詢結果
Parser–>解析器: 用來解析SQL接口中的SQL,分為語法解析和語義解析。解析后會生成一個語法樹,該語法樹可用于后續的查詢優化。解析器將SQL語句“肢解”為關鍵字、表名、字段名等內容
Optimlzer–>優化器: 核心組件,對SQL進行優化:分為邏輯上的優化和物理上的優化。物理優化—使用索引
Cache & Buffers–>查詢緩存: 在8.0中已經棄用。以key - value的方式緩存查詢結果,查詢結果作為value,SQL語句作為key。當下一次查詢和緩存的查詢語句完全一致時查詢命中
pluggable Storage Engines–>插件式存儲引擎: 與底層的文件系統進行交互
File system–>文件系統
File & Logs–>日志文件
5.7查詢順序: Connectors–>Connection Pool (連接池)–>SQL Interface(SQL接口)–>Cache & Buffers(查詢緩存)–>Parser(解析器)–>Optimlzer(優化器)–>pluggable Storage Engines(插件式存儲引擎)–>File system(文件系統)–>Cache & Buffers(查詢緩存)–>SQL Interface(SQL接口)
————————————————
1.2 Connectors
Connectors,指的是不同語言中與SQL的交互。MySQL首先是一個網絡程序,在TCP之上定義了自己的應用層協議。所以要使用MySQL,我們可以編寫代碼,跟MySQL Server建立TCP連接,之后按照其定義好的協議進行交互。或者比較方便的辦法是調用SDK,比如Native C API、JDBC、PHP等各語言MysQL Connector,或者通過ODBC。但通過SDK來訪問MysQL,本質上還是在TCP連接上通過MySQL協議跟MySQL進行交互。
接下來的MysQL Server結構可以分為如下的三層:
- 連接層
- 服務層
- 引擎層
1.3 第1層:連接層
系統(客戶端)訪問 MySQL 服務器前,做的第一件事就是建立 TCP 連接。
經過三次握手建立連接成功后, MySQL 服務器對 TCP 傳輸過來的賬號密碼做身份認證、權限獲取。
- 用戶名或密碼不對,會收到一個Access denied for user錯誤,客戶端程序結束執行
- 用戶名密碼認證通過,會從權限表查出賬號擁有的權限與連接關聯,之后的權限判斷邏輯,都將依賴于此時讀到的權限
接著我們來思考一個問題
一個系統只會和MySQL服務器建立一個連接嗎?只能有一個系統和MySQL服務器建立連接嗎?
當然不是,多個系統都可以和MySQL服務器建立連接,每個系統建立的連接肯定不止一個。所以,為了解決TCP 無限創建與TCP頻繁創建銷毀帶來的資源耗盡、性能下降問題。MySQL服務器里有專門的TCP連接池限制連接數, 采用長連接模式復用TCP連接,來解決上述問題。
TCP 連接收到請求后,必須要分配給一個線程專門與這個客戶端的交互。所以還會有個線程池,去走后面的流程。每一個連接從線程池中獲取線程,省去了創建和銷毀線程的開銷。
這些內容我們都歸納到MySQL的連接管理組件中。
所以連接管理的職責是負責認證,管理連接、獲取權限信息。
1.4 第2層:服務層
第二層架構主要完成大多數的核心服務功能,如SQL接口,并完成緩存的查詢,SQL的分析和優化及部分內置函數 的執行。所有跨存儲引擎的功能也在這一層實現,如過程、函數等。
在該層,服務器會解析查詢并創建相應的內部解析樹 ,并對其完成相應的優化:如確定查詢表的順序,是否利 用索引等,最后生成相應的執行操作。
如果是SELECT語句,服務器還會查詢內部的緩存。如果緩存空間足夠大,這樣在解決大量讀操作的環境中能夠很 好的提升系統的性能。
-
SQL Interface: SQL接口
- 接收用戶的SQL命令,并且返回用戶需要查詢的結果。比如SELECT … FROM就是調用SQL Interface
-
MySQL支持DML(數據操作語言)、DDL(數據定義語言)、存儲過程、視圖、觸發器、自定義函數等多種SQL語言接口
-
Parser: 解析器
-
在解析器中對 SQL 語句進行語法分析、語義分析。將SQL語句分解成數據結構,并將這個結構傳遞到后續步驟,以后SQL語句的傳遞和處理就是基于這個結構的。如果在分解構成中遇到錯誤,那么就說明這個SQL語句是不合理的。
-
在SQL命令傳遞到解析器的時候會被解析器驗證和解析,并為其創建語法樹 ,并根據數據字典豐富查詢語法樹,會驗證該客戶端是否具有執行該查詢的權限。創建好語法樹后,MySQL還會對SQl查詢進行語法上的優化,進行查詢重寫。
-
-
Optimizer: 查詢優化器
-
SQL語句在語法解析之后、查詢之前會使用查詢優化器確定 SQL 語句的執行路徑,生成一個執行計劃。
-
這個執行計劃表明應該使用哪些索引進行查詢(全表檢索還是使用索引檢索),表之間的連接順序如何,最后會 按照執行計劃中的步驟調用存儲引擎提供的方法來真正的執行查詢,并將查詢結果返回給用戶。
-
它使用“選取-投影-連接 ”策略進行查詢。例如:
-
這個SELECT查詢先根據WHERE語句進行 選取 ,而不是將表全部查詢出來以后再進行gender過濾。 這個SELECT查詢先根據id和name進行屬性投影 ,而不是將屬性全部取出以后再進行過濾,將這兩個查詢條件 連接 起來生成最終查詢結果。
- Caches & Buffers: 查詢緩存組件
- MySQL內部維持著一些Cache和Buffer,比如Query Cache用來緩存一條SELECT語句的執行結果,如果能夠在其中找到對應的查詢結果,那么就不必再進行查詢解析、優化和執行的整個過 程了,直接將結果反饋給客戶端。
- 這個緩存機制是由一系列小緩存組成的。比如表緩存,記錄緩存,key緩存,權限緩存等 。
- 這個查詢緩存可以在不同客戶端之間共享 。
- 從MySQL 5.7.20開始,不推薦使用查詢緩存,并在 MySQL 8.0中刪除 。
1.5 第3層:引擎層
和其它數據庫相比,MySQL有點與眾不同,它的架構可以在多種不同場景中應用并發揮良好作用,主要體現在存儲引擎的架構上,插件式的存儲引擎 架構將查詢處理和其它的系統任務以及數據的存儲提取相分離。這種架構可 以根據業務的需求和實際需要選擇合適的存儲引擎。同時開源的MySQL還允許開發人員設置自己的存儲引擎。
這種高效的模塊化架構為那些希望專門針對特定應用程序需求(例如數據倉庫、事務處理或高可用性情況)的人 提供了巨大的好處,同時享受使用一組獨立于任何接口和服務的優勢存儲引擎。
插件式存儲引擎層( Storage Engines),真正的負責了MySQL中數據的存儲和提取,對物理服務器級別維護的底層數據執行操作, 服務器通過API與存儲引擎進行通信。不同的存儲引擎具有的功能不同,這樣我們可以根據自己的實際需要進行選取。
MySQL 8.0.25默認支持的存儲引擎如下:
1.6 存儲層
所有的數據,數據庫、表的定義,表的每一行的內容,索引,都是存在文件系統上,以文件的方式存在的,并完成與存儲引擎的交互。當然有些存儲引擎比如InnoDB,也支持不使用文件系統直接管理裸設備,但現代文件系統的實現使得這樣做沒有必要了。在文件系統之下,可以使用本地磁盤,可以使用DAS、NAS、SAN等各種存儲系統。
1.7 小結
MySQL架構圖本節開篇所示。下面為了熟悉SQL執行流程方便,我們可以簡化如下:
簡化為三層結構:
2. SQL執行流程
2.1 MySQL 中的 SQL執行流程
MySQL的查詢流程:
1.查詢緩存:Server 如果在查詢緩存中發現了這條 SQL 語句,就會直接將結果返回給客戶端;如果沒有,就進入到解析器階段。需要說明的是,因為查詢緩存往往效率不高,所以在MySQL8.0 之后就拋棄了這個功能。
MySQL拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以key-value 對的形式,被直接緩存在內存中。key是查詢的語句,value是查詢的結果。如果你的查詢能夠直接在這個緩存中找到key,那么這個value就會被直接返回給客戶端。如果語句不在查詢緩存中,就會繼續后面的執行階段。執行完成后,執行結果會被存入查詢緩存中。所以,如果查詢命中緩存,MySQL不需要執行后面的復雜操作,就可以直接返回結果,這個效率會很高。
大多數情況查詢緩存就是個雞肋,為什么呢?
SELECT employee_id,last_name FROM employees WHERE employee_id = 101;查詢緩存是提前把查詢結果緩存起來,這樣下次不需要執行就可以直接拿到結果。需要說明的是,在MySQL 中的查詢緩存,不是緩存查詢計劃,而是查詢對應的結果。這就意味著查詢匹配的 魯棒性大大降低 ,只有 相同的查詢操作才會命中查詢緩存 。兩個查詢請求在任何字符上的不同(例如:空格、注釋、大小寫),都會導致緩存不會命中。因此 MySQL 的查詢緩存命中率不高 。
同時,如果查詢請求中包含某些系統函數、用戶自定義變量和函數、一些系統表,如 mysql 、information_schema、 performance_schema 數據庫中的表,那這個請求就不會被緩存。以某些系統函數舉例,可能同樣的函數的兩次調用會產生不一樣的結果,比如函數 NOW ,每次調用都會產生最新的當前時間,如果在一個查詢請求中調用了這個函數,那即使查詢請求的文本信息都一樣,那不同時間的兩次查詢也應該得到不同的結果,如果在第一次查詢時就緩存了,那第二次查詢的時候直接使用第一次查詢的結果就是錯誤的!
此外,既然是緩存,那就有它 緩存失效的時候 。MySQL的緩存系統會監測涉及到的每張表,只要該表的結構或者數據被修改,如對該表使用了 INSERT 、 UPDATE 、 DELETE 、 TRUNCATE TABLE 、 ALTER TABLE 、 DROP TABLE 或 DROP DATABASE 語句,那使用該表的所有高速緩存查詢都將變為無效并從高速緩存中刪除!對于 更新壓力大的數據庫 來說,查詢緩存的命中率會非常低。
總之,因為查詢緩存往往弊大于利,查詢緩存的失效非常頻繁。
一般建議大家在靜態表里使用查詢緩存,什么叫靜態表呢?就是一般我們極少更新的表。比如,一個系統配置表、字典表,這張表上的查詢才適合使們查詢緩存。好在MySQL也提供了這種"按需使用"的方式。你可以將my.cnf參數query_cache_type設置成DEMAND,代表當sql語句中有SQL_CACHE關鍵詞時才緩存。比如:
#query_cache_type有3個值:0代表關閉查詢緩存OFF、1代力開啟ON、2 〔DEMAND) uery_cache_type-2這樣對于默認的SQL語句都不使用查詢緩存。而對于你確定要使用查詢緩存的語句,可以用SQL_CACHE顯式指定,像下面這個語句—樣:
select SQL_CACHE * from test where ID=5; # SQL_NO_CACHE查看當前mysql實例是否開啟緩存機制
show variables like '%query_cache_type'; /*Mysql5.7輸出 +------------------+-------+ | Variable_name | Value | +------------------+-------+ | query_cache_type | OFF | +------------------+-------+ Mysql8.0輸出: Empty set (0.01 sec) */監控查詢緩存的命中率:
show status like '%Qcache%'; /*Mysql5.7輸出 +-------------------------+---------+ | Variable_name | Value | +-------------------------+---------+ | Qcache_free_blocks | 1 | | Qcache_free_memory | 1031872 | | Qcache_hits | 0 | | Qcache_inserts | 0 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 3 | | Qcache_queries_in_cache | 0 | | Qcache_total_blocks | 1 | +-------------------------+---------+ #Qcache_hits:查詢命中情況 */運行結果解析:
Qcache_free_blocks :.表示查詢緩存中還有多少剩余的blocks,如果該值顯示較大,則說明查詢緩存中的內存碎片過多了,可能在一定的時間進行整理。|
Qcache_free_memory :查詢緩存的內存大小,通過這個參數可以很清晰的知道當前系統的查詢內存是否夠用,是多了,還是不夠用,DBA可以根據實際情況做出調整。
Qcache_hits :表示有多少次命中緩存。我們主要可以通過該值來驗證我們的查詢緩存的效果。數字越大,緩存效果越理想。
Qcache_inserts:表示多少次未命中然后插入,意思是新來的SQL請求在緩存中未找到,不得不執行查詢處理,執行查詢處理后把結果insert到查詢緩存中。這樣的情況的次數越多,表示查詢緩存應用到的比較少,效果也就不理想。當然系統剛啟動后,查詢緩存是空的,這很正常。
Qcache_lowmem_prunes :該參數記錄有多少條查詢因為內存不足而被移除出查詢緩存。通過這個值,用戶可以適當的調整緩存大小。
Qcache_not_cached :表示因為query_cache_type的設置而沒有被緩存的查詢數量。
Qcache_queries_in_cache:當前緩存中緩存的查詢的數量。
Qcache_total_blocks :當前緩存的block數量。
2. 解析器:在解析器中對 SQL 語句進行語法分析、語義分析。
如果沒有命中查詢緩存,就要開始真正執行語句了。首先,MySQL需要知道你要做什么,因此需要對SQL語句做解析。SQL語句的分析分為詞法分析與語法分析。
分析器先做“ 詞法分析 ”。你輸入的是由多個字符串和空格組成的一條 SQL 語句,MySQL 需要識別出里面的字符串分別是什么,代表什么。 MySQL 從你輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字符串“T”識別成“表名 T”,把字符串“ID”識別成“列 ID”。
接著,要做“ 語法分析 ”。根據詞法分析的結果,語法分析器(比如:Bison)會根據語法規則,判斷你輸入的這個 SQL 語句是否 滿足 MySQL 語法 。
#語法錯誤:group by后面沒有job_id(詞對但是組合在一起出現了問題) select department_id,job_id,avg(salary) from employees group by department_id;如果你的語句不對,就會收到"You have an error in your SQL syntax"的錯誤提醒,比如語句from寫成了"rom"–>詞法錯誤。
# 語句 mysql> select *fro test 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 'fro test where id=1' at line 1 mysql>如果SQL語句正確,則會生成一個這樣的語法樹:
下面是SQL詞法分析的過程步驟:
至此解析器的工作結束,接下來進入到優化器。
3.優化器:在優化器中會確定 SQL 語句的執行路徑,比如是根據全表檢索 ,還是根據索引檢索等。
經過了解析器,MySQL就知道你要做什么了。在開始執行之前,還要先經過優化器的處理。一條查詢可以有很多種執行方式,最后都返回相同的結果。優化器的作用就是找到這其中最好的執行計劃。
比如:優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序,還有表達式簡化、子查詢轉為連接、外連接轉為內連接等。
舉例:如下語句是執行兩個表的 join:
select * from test1 join test2 using(ID) where test1.name='zhangwei' and test2.name='mysql高級課程'; 方案1:可以先從表 test1 里面取出 name='zhangwei’的記錄的 ID 值,再根據 ID 值 關聯到表test2,再判斷test2 里面 name的值是否等于 ‘mysql高級課程’。 方案2:可以先從表 test2 里面取出 name=‘mysql高級課程’ 的記錄的 ID 值, 再根據 ID 值關聯到 test1,再判斷 test1 里面 name的值是否等于 zhangwei。這兩種執行方法的邏輯結果是一樣的,但是執行的效率會有不同,而優化器的作用就是決定選擇 使用哪一個方案。優化器階段完成后,這個語句的執行方案就確定下來了,然后進入執行器階段。如果你還有一些疑問,比如優化器是怎么選擇索引的,有沒有可能選擇錯等。后面講到索引我們再談。在查詢優化器中,可以分為邏輯查詢 優化階段和物理查詢優化階段。
邏輯查詢優化就是通過改變SQL語句的內容來使得SQL查詢更高效,同時為物理查詢優化提供更多的候選執行計劃。通常采用的方式是對SQL語句進行等價變換,對查詢進行重寫,而查詢重寫的數學基礎就是關系代數。對條件表達式進行等價謂詞重寫、條件簡化,對視圖進行重寫,對子查詢進行優化,對連接語義進行了外連接消除、嵌套連接消除等。
物理查詢優化是基于關系代數進行的查詢重寫,而關系代數的每一步都對應著物理計算,這些物理計算往往存在多種算法。因此需要計算各種物理路徑的代價,從中選擇代價最小的作為執行計劃。在這個階段里,對于單表和多表連接的操作,需要高效地使用索引,提升直詢效率。
4. 執行器:
截止到現在,還沒有真正去讀寫真實的表,僅僅只是產出了一個執行計劃。于是就進入了執行器階段 。
在執行之前需要判斷該用戶是否具備權限 。如果沒有,就會返回權限錯誤。如果具備權限,就執行 SQL查詢并返回結果。在 MySQL8.0 以下的版本,如果設置了查詢緩存,這時會將查詢結果進行緩存。
select * from test where id=1;如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,調用存儲引擎API對表進行的讀寫。存儲引擎API只是抽象接口,下面還有個存儲引擎層,具體實現還是要看表選擇的存儲引擎。
比如:表 test 中,ID 字段沒有索引,那么執行器的執行流程是這樣的:
至此,這個語句就執行完成了。對于有索引的表,執行的邏輯也差不多。
SQL 語句在 MySQL 中的流程是: SQL語句→查詢緩存→解析器→優化器→執行器 。
2.2 MySQL8中SQL執行原理
前面的結構圖很復雜,我們需要抓取最核心的部分:SQL的執行原理。不同的DBMS的SQL的執行原理是相通的,只是在不同的軟件中,各有各的實現路徑。
既然一條SQL語句會經歷不同的模塊,那我們就來看下,在不同的模塊中。SQL執行所使用的資源(時間)是怎樣的。如何在MySQL中對一條 SQL語句的執行時間進行分析。
1.確認profiling 是否開啟
了解查詢語句底層執行的過程: select @@profiling;或者show variables like '%profiling%'查看是否開啟計劃。開啟它可以讓MySQL收集在SQL執行時所使用的資源情況,命令如下:
select @@profiling; /* +-------------+ | @@profiling | +-------------+ | 0 | +-------------+ */ show variables like 'profiling'; /* +---------------+-------+ | Variable_name | Value | +---------------+-------+ | profiling | OFF | +---------------+-------+ */
profiling=0 代表關閉,我們需要把 profiling 打開,即設置為 1:
Profiling功能由MySQL會話變量:profiling控制。默認是OFF(關閉狀態)
2.多次執行相同SQL查詢
然后我們執行一個 SQL 查詢(你可以執行任何一個 SQL 查詢):
select * from employees;3.查看profiles
show profiles和show profile語句可以展示當前會話(退出session后,profling重置為o)中執行語句的資源使用情況。
查看當前會話所產生的所有 profiles:
show profiles; # 顯示最近的幾次查詢 /*本機輸出: +----------+------------+-------------------------+ | Query_ID | Duration | Query | +----------+------------+-------------------------+ | 1 | 0.00031625 | select @@profiling | | 2 | 0.00019625 | select * from employees | +----------+------------+-------------------------+ */4.查看profile
顯示執行計劃,查看程序的執行步驟:
默認是最近的一次
當然你也可以查詢指定的 Query ID,比如:
可以看出都是17條,所以是間接證明8.0是沒有開啟緩存的
1、除了查看cpu、io阻塞等參數情況,還可以查詢下列參數的利用情況。
Syntax : SHOW PROFILE [type [,type] ... ] [ FOR QUERY n] [LIMIT row_count [OFFSET offset]] type : { ALL --顯示所有參數的開銷信息 BLOCK IO --顯示IO的相關開銷 CONTEXT SWITCHES -- 上下文切換和關開信息 CPU --顯示CPU的相關開銷 IPC --顯示發送和接收相關開銷信息 MEMORY --顯示內存相關開銷信息 PAGE FAULTS -- 顯示頁面錯讀和關開銷信息 SOURCE -- 顯示和Saurce_function, Source_file , Source_line 相關的開銷信息 SWAPS -- 顯示交換次數相關的開銷信息 }2.3 MySQL5.7中SQL執行原理
上述操作在MySQL5.7中測試,發現前后兩次相同的sql語句,執行的查詢過程仍然是相同的。不是會使用緩存嗎?這里我們需要顯式開啟查詢緩存模式 。
在MySQL5.7中如下設置:
1.配置文件中開啟查詢緩存
在 /etc/my.cnf 中新增一行:
query_cache_type=12.重啟mysql服務
systemctl restart mysqld3.開啟查詢執行計劃
由于重啟過服務,需要重新執行如下指令,開啟profiling。
set profiling=1;4.執行語句兩次:
select * from departments; select * from departments;5.查看profiles
6.查看profile
顯示執行計劃,查看程序的執行步驟:
show profile for query 7; show profile for query 8;
結論不言而喻。執行編號8時,比執行編號7時少了很多信息,可以看出查詢語句直接從緩存中獲取數據。
- 注意1:
例如:
#雖然查詢結果一致。但并沒有命中緩存 select * from mydb.mytbl where id=2 select * from mydb.mytbl where id>1 and id<3- 注意2:
同樣的開啟緩存的配置信息query_cache_type=1如果在MySQL8中添加。重啟服務時會報錯:
分別在MySQL5.7和 MySQL8中執行如下命令:
mysql> show variables like ' %query_cache%' ;- MysQL5.7中顯示:
- MysQL8.0中顯示:
2.4 SQL語法順序
隨著Mysql版本的更新換代,其優化器也在不斷的升級,優化器會分析不同執行順序產生的性能消耗不同而動態調整執行順序。
需求:查詢每個部門年齡高于20歲的人數且高于20歲人數不能少于2人,顯示人數最多的第一名部門信息下面是經常出現的查詢順序:
下面是經常出現的查詢順序:
2.5 Oracle中的SQL執行流程(了解)
Oracle 中采用了共享池來判斷 SQL 語句是否存在緩存和執行計劃,通過這一步驟我們可以知道應該采用硬解析還是軟解析。
我們先來看下 SQL 在 Oracle 中的執行過程:
從上面這張圖中可以看出,SQL 語句在 Oracle 中經歷了以下的幾個步驟。
1.語法檢查:檢查 SQL 拼寫是否正確,如果不正確,Oracle 會報語法錯誤。
2.語義檢查:檢查 SQL 中的訪問對象是否存在。比如我們在寫 SELECT 語句的時候,列名寫錯了,系統就會提示錯誤。語法檢查和語義檢查的作用是保證 SQL 語句沒有錯誤。
3.權限檢查:看用戶是否具備訪問該數據的權限。
4.共享池檢查:共享池(Shared Pool)是一塊內存池,最主要的作用是緩存 SQL 語句和該語句的執行計劃。Oracle 通過檢查共享池是否存在 SQL 語句的執行計劃,來判斷進行軟解析,還是硬解析。那軟解析和硬解析又該怎么理解呢?
在共享池中,Oracle 首先對 SQL 語句進行 Hash 運算 ,然后根據 Hash 值在庫緩存(Library Cache)中查找,如果存在 SQL 語句的執行計劃 ,就直接拿來執行,直接進入“執行器”的環節,這就是 軟解析 。
如果沒有找到 SQL 語句和執行計劃,Oracle 就需要創建解析樹進行解析,生成執行計劃,進入“優化器”這個步驟,這就是硬解析 。
5.優化器:優化器中就是要進行硬解析,也就是決定怎么做,比如創建解析樹,生成執行計劃。
.
6.執行器:當有了解析樹和執行計劃之后,就知道了 SQL 該怎么被執行,這樣就可以在執行器中執行語句了。
共享池是 Oracle 中的術語,包括了庫緩存,數據字典緩沖區等。我們上面已經講到了庫緩存區,它主要緩存 SQL 語句和執行計劃。而 數據字典緩沖區 存儲的是 Oracle 中的對象定義,比如表、視圖、索引等對象。當對 SQL 語句進行解析的時候,如果需要相關的數據,會從數據字典緩沖區中提取。
庫緩存 這一個步驟,決定了 SQL 語句是否需要進行硬解析。為了提升 SQL 的執行效率,我們應該盡量避免硬解析,因為在 SQL 的執行過程中,創建解析樹,生成執行計劃是很消耗資源的。
你可能會問,如何避免硬解析,盡量使用軟解析呢?
在 Oracle 中, 綁定變量是它的一大特色。綁定變量就是在 SQL 語句中使用變量,通過不同的變量取值來改變 SQL 的執行結果。這樣做的好處是能提升軟解析的可能性 ,不足之處在于可能會導致生成的執行計劃不夠優化,因此是否需要綁定變量還需要視情況而定。
舉個例子,我們可以使用下面的查詢語句:
select * from player where player_id = 10001;你也可以使用綁定變量,如:
select * from player where player_id = :player_id;這兩個查詢語句的效率在 Oracle 中是完全不同的。如果你在查詢 player_id = 10001 之后,還會查詢10002、10003 之類的數據,那么每一次查詢都會創建一個新的查詢解析。而第二種方式使用了綁定變量,那么在第一次查詢之后,在共享池中就會存在這類查詢的執行計劃,也就是軟解析。
因此,我們可以通過使用綁定變量來減少硬解析,減少 Oracle 的解析工作量。但是這種方式也有缺點,使用動態 SQL 的方式,因為參數不同,會導致 SQL 的執行效率不同,同時 SQL 優化也會比較困難。
Oracle的架構圖:
簡圖:
小結:
Oracle 和 MySQL 在進行 SQL 的查詢上面有軟件實現層面的差異。Oracle 提出了共享池的概念,通過共享池來判斷是進行軟解析,還是硬解析。
3. 數據庫緩沖池(buffer pool)
InnoDB 存儲引擎是以頁為單位來管理存儲空間的,我們進行的增刪改查操作其實本質上都是在訪問頁面(包括讀頁面、寫頁面、創建新頁面等操作)。而磁盤 I/O 需要消耗的時間很多,而在內存中進行操作,效率則會高很多,為了能讓數據表或者索引中的數據隨時被我們所用,DBMS 會申請 占用內存來作為數據緩沖池 ,在真正訪問頁面之前,需要把在磁盤上的頁緩存到內存中的 Buffer Pool 之后才可以訪問。
這樣做的好處是可以讓磁盤活動最小化,從而減少與磁盤直接進行 I/O 的時間 。要知道,這種策略對提升 SQL 語句的查詢性能來說至關重要。如果索引的數據在緩沖池里,那么訪問的成本就會降低很多。
3.1 緩沖池 vs 查詢緩存
緩沖池和查詢緩存是一個東西嗎?不是。
1. 緩沖池(Buffer Pool)
首先我們需要了解在 InnoDB 存儲引擎中,緩沖池都包括了哪些。
在 InnoDB 存儲引擎中有一部分數據會放到內存中,緩沖池則占了這部分內存的大部分,它用來存儲各種數據的緩存,如下圖所示:
從圖中,你能看到 InnoDB 緩沖池包括了數據頁、索引頁、插入緩沖、鎖信息、自適應 Hash 和數據字典信息等。
緩存池的重要性:
對于使用InnoDB作為存儲引擎的表來說,不管是用于存儲用戶數據的索引(包括聚簇索引和二級索引),還是各種系統數據,都是以頁的形式存放在表空間中的,而所謂的表空間只不過是InnoDB對文件系統上一個或幾個實際文件的抽象,也就是說我們的數據說到底還是存儲在磁盤上的。但是各位也都知道,磁盤的速度慢的跟烏龜一樣,怎么能配得上"快如風,疾如電"的CPU呢?這里,緩沖池可以幫助我們消除CPU和磁盤之間的鴻溝。所以InnoDB存儲引擎在處理客戶端的請求時,當需要訪問某個頁的數據時,就會把完整的頁的數據全部加載到內存中,也就是說即使我們只需要訪問一個頁的一條記錄,那也需要先把整個頁的數據加載到內存中。將整個頁加載到內存中后就可以進行讀寫訪問了,在進行完讀寫訪問之后并不著急把該頁對應的內存空間釋放掉,而是將其緩存起來,這樣將來有請求再次訪問該頁面時,就可以省去磁盤IO的開銷了。
緩存原則:
“ 位置 * 頻次 ”這個原則,可以幫我們對 I/O 訪問效率進行優化。
首先,位置決定效率,提供緩沖池就是為了在內存中可以直接訪問數據。
其次,頻次決定優先級順序。因為緩沖池的大小是有限的,比如磁盤有 200G,但是內存只有 16G,緩沖池大小只有 1G,就無法將所有數據都加載到緩沖池里,這時就涉及到優先級順序,會 優先對使用頻次高的熱數據進行加載 。
緩沖池的預讀特性:
了解了緩沖池的作用之后,我們還需要了解緩沖池的另一個特性:預讀。
緩沖池的作用就是提升IO效率,而我們進行讀取數據的時候存在一個"“局部性原理”,也就是說我們使用了一些數據,大概率還會使用它周周的一些數據,因此采用“預讀”的機制提前加載,可以減少未來可能的磁盤I/O操作。
2. 查詢緩存
那么什么是查詢緩存呢?
查詢緩存是提前把查詢結果緩存起來,這樣下次不需要執行就可以直接拿到結果。需要說明的是,在MySQL 中的查詢緩存,不是緩存查詢計劃,而是查詢對應的結果。因為命中條件苛刻,而且只要數據表發生變化,查詢緩存就會失效,因此命中率低。
緩沖池服務于數據庫整體的I/o操作,它們的共同點都是通過緩存的機制來提升效率。
3.2 緩沖池如何讀取數據
緩沖池管理器會盡量將經常使用的數據保存起來,在數據庫進行頁面讀操作的時候,首先會判斷該頁面是否在緩沖池中,如果存在就直接讀取,如果不存在,就會通過內存或磁盤將頁面存放到緩沖池中再進行讀取。
緩存在數據庫中的結構和作用如下圖所示:
如果我們執行 SQL 語句的時候更新了緩存池中的數據,那么這些數據會馬上同步到磁盤上嗎?
實際上,當我們對數據庫中的記錄進行修改的時候,首先會修改緩沖池中頁里面的記錄信息,然后數據庫會以一定的頻率刷新到磁盤上。注意并不是每次發生更新操作,都會立刻進行磁盤回寫。緩沖池會采用一種叫做checkpoint 的機制將數據回寫到磁盤上,這樣做的好處就是提升了數據庫的整體性能。
比如,當緩沖池不夠用時,需要釋放掉一些不常用的頁,此時就可以強行采用checkpoint的方式,將不常用的臟頁回寫到磁盤上,然后兩從緩沖池中將這些頁釋放掉。這里臟頁(dirty page)指的是緩沖池中被修改過的頁,與磁盤上的數據頁不一致。
3.3 查看/設置緩沖池的大小
如果你使用的是MySQL MyISAM存儲引擎,它只緩存索引,不緩存數據,對應的鍵緩存參數為key_buffer_size,你可以用它進行查看。
如果你使用的是 InnoDB 存儲引擎,可以通過查看 innodb_buffer_pool_size 變量來查看緩沖池的大小。命令如下:
show variables like 'innodb_buffer_pool_size'; /*輸出 +-------------------------+-----------+ | Variable_name | Value | +-------------------------+-----------+ | innodb_buffer_pool_size | 134217728 | +-------------------------+-----------+ */你能看到此時 InnoDB 的緩沖池大小只有 134217728/1024/1024=128MB。我們可以修改緩沖池大小,比如改為256MB,方法如下:
set global innodb_buffer_pool_size = 268435456;或者:配置文件中修改
[server] innodb_buffer_pool_size = 2684354563.4 多個Buffer Pool實例
Buffer Pool本質是InnoDB向操作系統申請的一塊連續的內存空間,在多線程環境下,訪問Buffer Pool中的數據都需要加鎖處理。在Buffer Pool特別大而且多線程并發訪問特別高的情況下,單一的Buffer Pool可能會影響請求的處理速度。所以在Buffer Pool特別大的時候,我們可以把它們拆分成若干個小的Buffer Pool,每個Buffer Pool都稱為一個實例,它們都是獨立的,獨立的去中請內存空間,獨立的管理各種鏈表。所以在多線程并發訪問時并不會相互影響,從而提高并發處理能力。
可以在服務器啟動的時候通過設置innodb_buffer_pool_instances的值來修改Buffer Pool實例的個數:
[server] innodb_buffer_pool_instances = 2這樣就表明我們要創建2個 Buffer Pool 實例。
我們看下如何查看緩沖池的個數,使用命令:
show variables like 'innodb_buffer_pool_instances';
那每個 Buffer Pool 實例實際占多少內存空間呢?其實使用這個公式算出來的:
也就是總共的大小除以實例的個數,結果就是每個 Buffer Pool 實例占用的大小。
不過也不是說Buffer Pool實例創建的越多越好,分別管理各個Buffer Pool也是需要性能開銷的,InnoDB規定:當innodb_buffer_pool_size的值小于1G的時候設置多個實例是無效的,InnoDB會默認把innodb_buffer_pool_instances 的值修改為1。而我們鼓勵在Bufer Pool大于或等于1G的時候設置多個Buffer Pool實例。
3.5 引申問題
Buffer Pool是MySQL內存結構中十分核心的一個組成,你可以先把它想象成一個黑盒子。
黑盒下的更新數據流程
當查詢數據的時候,會先去Buffer Pool中查詢。如果Buffer Pool中不存在,存儲引擎會先將數據從磁盤加載到Buffer Pool中,然后將數據返回給客戶端;同理,當更新某個數據的時候,如果這個數據不存在于BufferPool,同樣會先將數據加載進來,然后修改內存的數據。被修改過的數據會在之后統一刷入磁盤。
這個過程看似沒啥問題,實則是有問題的。假設修改Buffer Pool中的數據成功,但是還沒來得及將數據刷入磁盤MySQL就掛了怎么辦?按照上圖的邏輯,此時更新之后的數據只存在于Buffer Pool中,如果此時MySQL宕機了,這部分數據將會永久地丟失;
再者,更新到一半突然發生錯誤了,想要回滾到更新之前的版本,該怎么辦?連數據持久化的保證、事務回滾都做不到還談什么崩潰恢復?
答案:Redo Log & Undo Log
Redo Log :解決刷盤時刷到一半宕機的問題
Undo Log:解決回滾的問題
2022/7/30 19:16
最后
p109~p112
Markdown 20114 字數 722 行數
HTML 16741 字數 383 段落
總結
以上是生活随笔為你收集整理的第04章 逻辑架构【1.MySQL架构篇】【MySQL高级】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis 超时key过期监听
- 下一篇: Redis主从、哨兵及集群搭建