SQL Query 的一天過得好嗎 (上)?

關於 SQL 的一天

這篇我們會從使用者下了 SQL 指令開始、路途中經過連線池 (connection pool)、解析器 (parser)、優化器 (optimizer)… 等的過程紀錄下,希望這篇文章有幫助到你 🙂

SQL query 的一天

Phase 1 – Connection Pool (連線池)

1-1. 什麼是 Connection Pool?

Connection Pool 連接池

如果把 Connection Pool 想像成一間公司,那公司的 HR 部門就像是連線池,負責招募員工 (資料庫連線) 來處理處理大量的任務。

  1. 招募員工數 (pool_size):pool_size=10,HR 先預估的業務需求,決定先招募 10 名員工準備在公司中工作。
  2. 員工最長工時 (pool_recycle)pool_recycle=3600,代表 HR 部門每隔一小時 (3600秒) 會檢查一次員工的工作狀況,如果發現有員工 (資料庫連線) 已經工作了很長時間,為了避免潛在的過勞問題或是連接過時,HR 會決定讓這名員工休息。
  3. 等待時間 (pool_timeout) : pool_timeout=30,表示當所有員工都在忙碌時,如果有新的任務 (資料庫請求) 到來,公司會讓這個任務等待最多 30 秒。如果 30 秒後仍然沒有員工空閒,則會返回錯誤,告知目前員工太忙無法接受新任務。
  4. 根據業務量調整人手 (max_overflow):max_overflow=5,表示在正常工作人數 (pool_size)之上,如果突然來了很多任務,HR 部門可以臨時招募最多 5 名員工來處理額外的工作量。

透過這種方式,公司 (應用程式) 可以更高效地處理任務 (資料庫操作),同時保持對人力資源 (資料庫連線) 的有效管理,避免資源浪費。

1-2. Web Server (FastAPI) 的 Connection Pool

以下的設定就像我們剛剛的 HR,設定了總員工數、工時、等待時間和額外招募人數:

  • pool_size=10:連線池的大小 (總員工數)
  • max_overflow=10:指定在連線池達到 pool_size 後可以創建的最大連線數,如果設定 -1 則可以無限大 (額外招募)
  • pool_timeout=30:是當所有連線都在使用時,新請求等待獲得可用連線的最長時間(秒)(客戶等待時間)
  • pool_recycle=1800:超過這個時間連線就會被關閉並重新建立,以避免資料庫連線過時 (員工最長工時)

1-3. DB Server (MySQL) 的 Connection Pool

MySQL 的 server 也可以設定類似的參數,想了解更多可以從 SHOW VARIABLES 來查看

Phase 2 – Parser (解析器)

Parser 解析器

2-1. 什麼是 Parser?

Parser (解析器) 最主要功能是進行「語法檢查」,和將人類的查詢文字轉換為機器可以理解結構化形式的「 (AST 抽象與法樹)」, 供後續階段 Optimizer (優化器) 處理。

這個過程可以細分為以下幾個主要階段:

Parser AST 解析樹

2-1-1. 詞法分析 (Lexical Analysis)

  1. 將 SQL 查詢分解成一系列基本的單元或符號,稱為標記(tokens)。這些標記包括關鍵字、常量、操作符等。
  2. 例如把 “SELECT name FROM foo” 分解成 “SELECT”, “name”, “FROM”, “foo” 等標記。

2-1-2. 語法分析 (Syntax Analysis)

  1. 檢查這些標記是否按照 SQL 的語法規則正確組合,在這個階段,會生成一個抽象語法樹(AST),是一種表達語法結構和標記關係的樹狀結構。
  2. AST 清晰地反映了查詢的結構,每一個節點代表查詢中的一種結構 (例如表達式、操作符等)。高度結構化的表示,使得後續步驟可以更加有效地進行。

Parser 延伸閱讀

Phase 3 – Optimizer (優化器)

Optimizer 優化器

在 Optimizer 優化器的過程中,主要會分成兩個階段,分別是邏輯優化和基於成本優化:

3-1. 邏輯優化 (Logical Optimization)

這一階段的目的是在「不改變查詢結果」的前提下,重新構造查詢,例如:刪除無用的邏輯,像是底下是一個簡單的 SQL 查詢:

但如果我們使用 EXPLAIN 來看,可以看到優化器自動刪除了 1 = 1 的部分:

3-2. 基於成本優化器 (CBO, Cost-Based Optimizer)

這邊的 Cost 代表了 Server Cost + Engine Cost,也就是 CPU Cost + IO Cost,使用不同的 index 來查表的話,會有不同的 Cost,而 CBO 是取成本最少的來進行查詢。

成本的部分,可以從 engine_costengine_cost 兩張表查詢成本:

3-2-1. Server Cost (CPU Cost)

(select * from mysql.servert_cost;)

mysql servert_cost

3-2-2. Engine Cost (IO Cost)

(select * from mysql.engine_cost;)

mysql engine_cost

(可以看到從硬碟是記憶體讀取成本的 4 倍)

3-2-3. 簡單範例

我們今天針對 table_A 做 query (explain format=json select * from table_A;)

可以看到 query_cost = 0.65,這 cost 是從 Server Cost + Engine Cost 所獲得

  1. Engine Cost (IO Cost):
    • 16384 / 16 / 1024 = 1 (每頁預設 16KB,然後 1KB = 1024 字節,得到讀取多少頁)
    • 1 * 0.25 (memory_block_read_cost) = 0.25
  2. Server Cost (CPU Cost):
    • 4 * 0.1 (row_evaluate_cost) = 0.4

*補充:16384 是 Data_length,4 是 Rows,可以從這邊取的 show table status like 'table_A';

0.25 + 0.4 = 0.65,也就是我們這次的 query_cost,以上就是一個簡單的 CBO 成本計算的過程。

CBO 延伸閱讀


那本篇「SQL Query 的一天過得好嗎 (上)」就在這邊告一個段落,我們經歷了 SQL 的連線池 (connection pool)、解析器 (parser)、優化器 (optimizer),在下集我們會繼續 SQL 的下半,感謝收看 🙂

延伸閱讀

▍資料庫相關:

▍其他相關教學目錄:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *