C-04.MySQL邏輯架構

长名06發表於2024-03-25

C-04.邏輯架構

1.邏輯架構剖析

1.1 伺服器處理客戶端請求

首先MySQL是典型的C/S架構,即Client/Server架構,客戶端使用的是mysql,伺服器端程式使用的mysqld

不論客戶端程序和伺服器程序是採用那種方式進行通訊,最後實現的效果都是:客戶端程序向伺服器程序傳送一段文字(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 id,name FROM student WHERE gender = '女';

    • 這個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層:引擎層

外掛式儲存引擎層(Storage Engines),真正的負責了MySQL中資料的儲存和提取,對物理伺服器級別維護的底層資料執行操作,伺服器透過API與儲存引擎進行通訊。不同的儲存引擎具有的功能不同,這樣我們可以根據自己的實際需要進行選取。

MySQL8.0.25預設支援的儲存引擎如下

1.6 儲存層

所有的資料,資料庫、表的定義,表的每一行的內容,索引,都是存在檔案系統上,以檔案的方式存在的,並完成與儲存引擎的互動。當然有些儲存引擎比如InnoDB,也支援不使用檔案系統直接管理裸裝置,但現代檔案系統的實現使得這樣做沒有必要了。在檔案系統之下,可以使用本地磁碟,可以使用DAS、NAS、SAN等各種儲存系統。

1.7 小結


簡化為三層結構:

  1. 連線層:客戶端和伺服器端建立連線,客戶端傳送 SQL 至伺服器端;
  2. SQL 層(服務層):對 SQL 語句進行查詢處理;與資料庫檔案的儲存方式無關;
  3. 儲存引擎層:與資料庫檔案打交道,負責資料的儲存和讀取。

2.SQL執行流程

2.1 MySQL中的SQL執行流程


MySQL的查詢流程

1.查詢快取:Server 如果在查詢快取中發現了這條 SQL 語句,就會直接將結果返回給客戶端;如果沒有,就進入到解析器階段。需要說明的是,因為查詢快取往往效率不高,所以在 MySQL8.0 之後就拋棄了這個功能。

大多數情況查詢快取就是個雞肋,為什麼呢?

查詢快取是提前把查詢結果快取起來,這樣下次不需要執行就可以直接拿到結果。需要說明的是,在MySQL 中的查詢快取,不是快取查詢計劃,而是查詢對應的結果。這就意味著查詢匹配的魯棒性大大降低,只有 相同的查詢操作才會命中查詢快取 。兩個查詢請求在任何字元上的不同(例如:空格、註釋、大小寫),都會導致快取不會命中。因此 MySQL 的查詢快取命中率不高

魯棒是Robust的音譯,也就是健壯和強壯的意思。它是在異常和危險情況下系統生存的能力。

同時,如果查詢請求中包含某些系統函式、使用者自定義變數和函式、一些系統表,如 mysql 、information_schema、 performance_schema 資料庫中的表,那這個請求就不會被快取。以某些系統函式舉例,可能同樣的函式的兩次呼叫會產生不一樣的結果,比如函式NOW,每次呼叫都會產生最新的當前時間,如果在一個查詢請求中呼叫了這個函式,那即使查詢請求的文字資訊都一樣,那不同時間的兩次查詢也應該得到不同的結果,如果在第一次查詢時就快取了,那第二次查詢的時候直接使用第一次查詢的結果就是錯誤的!

此外,既然是快取,那就有它快取失效的時候。MySQL的快取系統會監測涉及到的每張表,只要該表的結構或者資料被修改,如對該表使用了INSERT 、 UPDATE 、 DELETE 、 TRUNCATE TABLE 、 ALTER TABLE 、 DROP TABLE 或 DROP DATABASE語句,那使用該表的所有快取記憶體查詢都將變為無效並從快取記憶體中刪除!對於更新壓力大的資料庫來說,查詢快取的命中率會非常低。

總之,因為查詢快取往往弊大於利,查詢快取的失效非常頻繁。

一般建議大家在靜態表裡使用查詢快取,什麼叫靜態表呢?就是一般我們極少更新的表。比如,一個系統配置表、字典表,這張表上的查詢才適合使用查詢快取。不過MySQL對於查詢快取提供了按需使用的方式。可以將my.cnd引數query_cache_type設定成DEMAND,代表當sql語句中有SQL_CACHE關鍵詞時,才快取。比如:

#query_cache_type有3個值,0代表關閉,1代表開啟,2代表DEMAND
query_cache_type=2

這樣對於預設的sql語句都不適用查詢快取,而對於確定要使用的查詢快取語句,可以用SQL_CACHE顯示指定

select SQL_CACHE * FROM test where id = 5;

檢視,是否開啟查詢快取

mysql> select @@global.query_cache_type;
+---------------------------+
| @@global.query_cache_type |
+---------------------------+
| ON                        |
+---------------------------+
1 row in set, 1 warning (0.00 sec)

#mysql8.0中執行
mysql> select @@global.query_cache_type;
ERROR 1193 (HY000): Unknown system variable 'query_cache_type'

監控插敘快取的命中率

mysql> show status like '%Qcache%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| Qcache_free_blocks      | 1       |
| Qcache_free_memory      | 1021240 |
| Qcache_hits             | 1       |
| Qcache_inserts          | 1       |
| Qcache_lowmem_prunes    | 0       |
| Qcache_not_cached       | 7       |
| Qcache_queries_in_cache | 1       |
| Qcache_total_blocks     | 4       |
+-------------------------+---------+
8 rows in set (0.00 sec)

Qcache_free_blocks:表示查詢快取中還有多少剩餘的blocks,如果該值顯示較大,則說明查詢快取中的記憶體碎片過多了,可能在一定的時間進行整理。

Qcache_free_memory :查詢快取的記憶體大小,透過這個引數可以很清晰的知道當前系統的查詢記憶體是否夠用,是多了,還是不夠用,DBA可以根據實際情況做出調整。

Qcache_hits :表示有多少次命中快取。我們主要可以透過該值來驗證我們的查詢快取的效果。數字越大,快取效果越理想。

Qcache_inserts:表示多少次未命中然後插入,意思是新來的SQL請求在快取中未找到,不得不執行查詢處理,執行查詢處理後把結果insert到查詢快取中。這樣的情況的次數越多,表示查詢快取應用到的比較少,效果也就不理想。當然系統剛啟動後,查詢快取是空的,這很正常。
Qcache_lowmem_prunes:該引數記錄有多少條查詢因為記憶體不足而被移除出查詢快取。透過這個值使用者可以適當的調整快取大小。

Ocache_not_cached:表示因為query_cache_type的設定而沒有被快取的查詢數量。

Qcache_queries_in_cache:當前快取中快取的查詢數量

Qcache_total_blocks :當前快取的block數量。

2.解析器:在解析器中對 SQL 語句進行語法分析、語義分析。


分析器先做“詞法分析”。你輸入的是由多個字串和空格組成的一條 SQL 語句,MySQL 需要識別出裡面的字串分別是什麼,代表什麼。 MySQL 從你輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字串“T”識別成“表名 T”,把字串“ID”識別成“列 ID”。

接著,要做“語法分析”。根據詞法分析的結果,語法分析器(比如:Bison)會根據語法規則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法

#該語句,詞法解析正確,語法錯誤
select department_id,job_id,avg(salary) from employees group by department_id;

如果SQL語句正確,則會生成一個這樣的語法樹:

3.最佳化器:在最佳化器中會確定 SQL 語句的執行路徑,比如是根據全表檢索,還是根據索引檢索等。

經過了解析器,MySQL就知道要做什麼了。在開始執行之前,還要先經過最佳化器的處理。一條查詢可以有多種執行方式,最後都返回相同的結果。最佳化器的作用就是找到這些執行計劃中,執行時間最短的執行計劃。

舉例:如下語句是執行兩個表的 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 以下的版本,如果設定了查詢快取,這時會將查詢結果進行快取。

如果有許可權,就開啟表繼續執行。開啟表的時候,執行器會根據表的引擎定義,呼叫儲存引擎API對錶進行讀寫。儲存引擎API只是抽象介面,下面還有儲存引擎層,具體實現,看錶的儲存引擎。

select * from test where id=1;

比如:表 test 中,ID 欄位沒有索引,那麼執行器的執行流程是這樣的:

/*呼叫 InnoDB 引擎介面取這個表的第一行,判斷 ID 值是不是1,如果不是則跳過,如果是則將這行存在結果集中;

呼叫引擎介面取“下一行”,重複相同的判斷邏輯,直到取到這個表的最後一行。

執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端。*/

至此,這個語句就執行完成了。對於有索引的表,執行的邏輯也差不多。

SQL 語句在 MySQL 中的流程是: SQL語句→查詢快取→解析器→最佳化器→執行器 。

2.2 MySQL8中SQL執行原理

2.2.1 確認profiling是否開啟
mysql> select @@profiling;

mysql> show variables like 'profiling';
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set, 1 warning (0.00 sec)

profiling=0 代表關閉,我們需要把 profiling 開啟,即設定為 1:

注意,profiling只能在會話層面開啟,不能配置到my.cnf中全域性,開啟,如果配置,在mysqld重啟時,會報錯。

這也證明,mysql系統引數,並不是都能在my.cnf檔案中配置使用,有些是在安裝時,就指定了,比如表名的大小寫等。

mysql> set profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
2.2.2 多次執行相同SQL查詢
mysql> select * from employees;

mysql> select * from employees;
2.2.3 檢視profiles

檢視當前會話所產生的所有profiles

mysql> show profiles;
+----------+------------+-------------------------+
| Query_ID | Duration   | Query                   |
+----------+------------+-------------------------+
|        1 | 0.00036725 | SELECT DATABASE()       |
|        2 | 0.01012700 | show databases          |
|        3 | 0.00398050 | show tables             |
|        4 | 0.00237800 | select * from employees |
|        5 | 0.00043100 | select * from employees |
+----------+------------+-------------------------+
5 rows in set, 1 warning (0.00 sec)
2.2.4 檢視profile

顯示執行計劃,檢視程式的執行步驟:

mysql> show profile;#預設顯示最近一次sql語句的執行計劃


也可以查詢指定的Query ID

mysql> show profile for query 5;

也可以查詢CPU,IO相關的內容

mysql>  show profile cpu,block io for query 5;
+--------------------------------+----------+----------+------------+--------------+---------------+
| Status                         | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------------------+----------+----------+------------+--------------+---------------+
| starting                       | 0.000101 | 0.000021 |   0.000078 |            0 |             0 |
| Executing hook on transaction  | 0.000007 | 0.000001 |   0.000003 |            0 |             0 |
| starting                       | 0.000007 | 0.000001 |   0.000005 |            0 |             0 |
| checking permissions           | 0.000005 | 0.000001 |   0.000004 |            0 |             0 |
| Opening tables                 | 0.000025 | 0.000005 |   0.000021 |            0 |             0 |
| init                           | 0.000005 | 0.000001 |   0.000003 |            0 |             0 |
| System lock                    | 0.000007 | 0.000002 |   0.000005 |            0 |             0 |
| optimizing                     | 0.000055 | 0.000056 |   0.000000 |            0 |             0 |
| statistics                     | 0.000014 | 0.000013 |   0.000000 |            0 |             0 |
| preparing                      | 0.000012 | 0.000012 |   0.000000 |            0 |             0 |
| executing                      | 0.000119 | 0.000119 |   0.000000 |            0 |             0 |
| end                            | 0.000004 | 0.000003 |   0.000000 |            0 |             0 |
| query end                      | 0.000023 | 0.000024 |   0.000000 |            0 |             0 |
| waiting for handler commit     | 0.000008 | 0.000007 |   0.000000 |            0 |             0 |
| closing tables                 | 0.000007 | 0.000007 |   0.000000 |            0 |             0 |
| freeing items                  | 0.000026 | 0.000027 |   0.000000 |            0 |             0 |
| cleaning up                    | 0.000008 | 0.000007 |   0.000000 |            0 |             0 |
+--------------------------------+----------+----------+------------+--------------+---------------+
17 rows in set, 1 warning (0.00 sec)

2.3 MySQL 5.7中SQL執行原理

2.3.1 配置檔案中開啟查詢快取

在/etc/my.cnf中新增一行

query_chche_type=1
2.3.2 重啟mysql服務
systemctl restart mysqld
2.3.3 開啟查詢執行計劃
mysql> set profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
2.3.4 執行兩次sql
mysql> select * from employees;

mysql> select * from employees;
2.3.5 檢視profiles
mysql> show profiles;
+----------+------------+-------------------------+
| Query_ID | Duration   | Query                   |
+----------+------------+-------------------------+
|        1 | 0.00018050 | SELECT DATABASE()       |
|        2 | 0.00053900 | show databases          |
|        3 | 0.00014850 | show tables             |
|        4 | 0.00008875 | select * from employees |
|        5 | 0.00004925 | select * from employees |
+----------+------------+-------------------------+
5 rows in set, 1 warning (0.00 sec)
2.3.6 檢視執行計劃

顯示執行計劃,檢視程式的執行步驟:

mysql> show profile for query 4;
+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000024 |
| Waiting for query cache lock   | 0.000005 |
| starting                       | 0.000003 |
| checking query cache for query | 0.000012 |
| checking privileges on cached  | 0.000003 |
| checking permissions           | 0.000007 |
| sending cached result to clien | 0.000030 |
| cleaning up                    | 0.000006 |
+--------------------------------+----------+
8 rows in set, 1 warning (0.00 sec)

從截圖中可以看出查詢語句直接從快取中獲取資料。

2.4 SQL語法順序

3.資料庫緩衝池(Buffer pool 瞭解)

InnoDB儲存引擎是以頁為單位來管理儲存空間的,我們進行的增刪改查操作其實本質上都是在訪問頁面(包括讀頁面、寫頁面、建立新頁面等操作)。而磁碟 I/O 需要消耗的時間很多,而在記憶體中進行操作,效率則會高很多,為了能讓資料表或者索引中的資料隨時被我們所用,DBMS 會申請佔用記憶體來作為資料緩衝池,在真正訪問頁面之前,需要把在磁碟上的頁快取到記憶體中的Buffer Pool之後才可以訪問。

這樣做的好處是可以讓磁碟活動最小化,從而減少與磁碟直接進行 I/O 的時間。要知道,這種策略對提升 SQL 語句的查詢效能來說至關重要。如果索引的資料在緩衝池裡,那麼訪問的成本就會降低很多。

3.1 緩衝池 VS 查詢快取

緩衝池和查詢快取是一個東西嗎?不是。

3.1.1 緩衝池(Buffer Pool)

首先我們需要了解在 InnoDB 儲存引擎中,緩衝池都包括了哪些。

在 InnoDB 儲存引擎中有一部分資料會放到記憶體中,緩衝池則佔了這部分記憶體的大部分,它用來儲存各種資料的快取,如下圖所示:


從圖中,你能看到 InnoDB 緩衝池包括了資料頁、索引頁、插入緩衝、鎖資訊、自適應 Hash 和資料字典資訊等。

快取池的重要性:

對於使用InnoDB作為儲存引擎的表來說,不管是用於儲存使用者資料的索引(包括聚簇索引和二級索引),還是各種系統資料,都是以

形式存放在表空間中的,而所謂的表空間只不過是InnoDB對檔案系統上一個或幾個實際檔案的抽象,也就是說我們的資料說到底還是存

儲在磁碟上的。但是磁碟的速度慢的跟烏龜一樣,怎麼能配得上“快如風,疾如電”的CPU呢?這裡,緩衝池可以幫助我們消除CPU和磁碟之

間的鴻溝。所以InnoDB儲存引擎在處理客戶端的請求時,當需要訪問某個頁的資料時,就會把完整的頁的資料全部載入到記憶體中,也就

是說即使我們只需要訪問一個頁的一條記錄,那也需要先把整個頁的資料載入到記憶體中。將整個頁載入到記憶體中後就可以進行讀寫訪問

了,在進行完讀寫訪問之後並不著急把該頁對應的記憶體空間釋放掉,而是將其快取起來,這樣將來有請求再次訪問該頁面時,就可以省去磁碟IO的開銷了。

快取原則:

位置 * 頻次”這個原則,可以幫我們對 I/O 訪問效率進行最佳化。

首先,位置決定效率,提供緩衝池就是為了在記憶體中可以直接訪問資料。其次,頻次決定優先順序順序。因為緩衝池的大小是有限的,比如磁碟有 200G,但是記憶體只有 16G,緩衝池大小隻有 1G,就無法將所有資料都載入到緩衝池裡,這時就涉及到優先順序順序,會優先對使用頻次高的熱資料進行載入

緩衝池的預讀特性:

瞭解了緩衝池的作用之後,我們還需要了解緩衝池的另一個特性:預讀。

緩衝池的作用就是提升I/o效率,而我們進行讀取資料的時候存在一個“區域性性原理”,也就是說我們使用了一些資料,大機率還會使用它周圍的一些資料,因此採用“預讀”的機制提前載入,可以減少未來可能的磁碟I/O操作。

3.1.2 查詢快取

那麼什麼是查詢快取呢?

查詢快取是提前把 查詢結果快取 起來,這樣下次不需要執行就可以直接拿到結果。需要說明的是,在MySQL 中的查詢快取,不是快取查

詢計劃,而是查詢對應的結果。因為命中條件苛刻,而且只要資料表發生變化,查詢快取就會失效,因此命中率低。

緩衝池服務於資料庫整體的I/O操作,它們的共同點都是透過快取的機制來提升效率。

3.2 緩衝池如何讀取資料

緩衝池管理器會盡量將經常使用的資料儲存起來,在資料庫進行頁面讀操作的時候,首先會判斷該頁面是否在緩衝池中,如果存在就直接

讀取,如果不存在,就會透過記憶體或磁碟將頁面存放到緩衝池中再進行讀取。

快取池在資料庫中的結構和作用如下圖所示:


如果我們執行 SQL 語句的時候更新了快取池中的資料,那麼這些資料會馬上同步到磁碟上嗎?

實際上,當我們對資料庫中的記錄進行修改的時候,首先會修改緩衝池中頁裡面的記錄資訊,然後資料庫會以一定的頻率重新整理到磁碟上。

注意並不是每次發生更新操作,都會立刻進行磁碟迴寫。緩衝池會採用一種叫做checkpoint 的機制將資料回寫到磁碟上,這樣做的好處

就是提升了資料庫的整體效能。

比如,當緩衝池不夠用時,需要釋放掉一些不常用的頁,此時就可以強行採用checkpoint 的方式將不常用的髒頁回寫到磁碟上,然後再

從緩衝池中將這些頁釋放掉。這裡髒頁(dirty page)指的是緩衝池中被修改過的頁,與磁碟上的資料頁不一致。

3.3 檢視/設定緩衝池的大小

MyIASM儲存引擎,只快取索引,不快取資料,對應的鍵快取引數為key_buffer_size

如果你使用的是 InnoDB 儲存引擎,可以透過檢視 innodb_buffer_pool_size 變數來檢視緩衝池的大小。命令如下:

mysql> select @@global.innodb_buffer_pool_size;
+----------------------------------+
| @@global.innodb_buffer_pool_size |
+----------------------------------+
|                        134217728 |
+----------------------------------+
1 row in set (0.01 sec)

你能看到此時 InnoDB 的緩衝池大小隻有 134217728/1024/1024=128MB。我們可以修改緩衝池大小,比如改為256MB,方法如下:

set global innodb_buffer_pool_size = 268435456;

或者

[server]
innodb_buffer_pool_size = 268435456 #需要重啟mysql服務
mysql> select @@global.innodb_buffer_pool_size;
+----------------------------------+
| @@global.innodb_buffer_pool_size |
+----------------------------------+
|                        268435456 |
+----------------------------------+
1 row in set (0.00 sec)

3.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 例項。我們看下如何檢視緩衝池的個數,使用命令:

mysql> select @@global.innodb_buffer_pool_instances;
+---------------------------------------+
| @@global.innodb_buffer_pool_instances |
+---------------------------------------+
|                                     1 |
+---------------------------------------+
1 row in set (0.00 sec)

每個Buffer pool的大小為

#innodb_buffer_pool_size/innodb_buffer_pool_instances
#也就是總共的大小除以例項的個數,結果就是每個 Buffer Pool 例項佔用的大小。

不過也不是說Buffer Pool例項建立的越多越好,分別管理各個Buffer Pool也是需要效能開銷的,InnoDB規定:innodb_buffer_pool_size的

值小於1G的時候設定多個例項是無效的,InnoDB會預設把innodb_buffer_pool_instances的值修改為1。而我們鼓勵在Buffer 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

只是為了記錄自己的學習歷程,且本人水平有限,不對之處,請指正。

相關文章