你的查詢為什麼這麼慢?從資料庫設計看後端工程師的基本功
身為後端工程師,CRUD 寫多了,很容易把資料庫當成黑盒子。遇到效能問題,第一反應就是「加個索引試試」。但索引不是萬靈丹,資料庫效能調優更不是玄學。本文將從資料庫設計的角度出發,帶你理解查詢背後的原理,擺脫「索引大法好」的迷思,真正掌握後端工程師的基本功。
索引不是萬靈丹
B-Tree 索引的原理
最常見的索引類型是 B-Tree 索引。它像一棵平衡樹,讓你可以快速找到特定值的資料列。想像一下,你要在一本沒有目錄的書裡找特定章節,只能一頁一頁翻。有了目錄(索引),就可以直接跳到目標頁面。
但索引並非免費午餐。每次新增、修改、刪除資料,索引也需要更新,這會增加寫入操作的成本。而且,索引會佔用額外的儲存空間。
Covering Index (覆蓋索引)
當你的查詢只需要索引就能提供所有需要的資料時,就稱為 Covering Index。這時候資料庫不需要回表 (table lookup) 查詢原始資料列,效能會大幅提升。
例如,假設我們有一個 users 表格,欄位有 id, name, email, age。
|
|
如果我們執行以下查詢:
|
|
因為 idx_name_email 索引包含了 name 和 email 兩個欄位,資料庫可以直接從索引中取得結果,而不需要讀取 users 表格的原始資料。
過多索引的副作用
過多的索引會導致:
- 寫入效能下降:每次寫入操作都需要更新所有索引。
- 儲存空間增加:索引會佔用額外的儲存空間。
- 查詢最佳化器混亂:資料庫的查詢最佳化器可能會選擇錯誤的索引,導致效能下降。
因此,索引的設計需要謹慎考慮,只建立必要的索引。
正規化 vs 反正規化
正規化的優缺點
正規化旨在減少資料冗餘,提高資料一致性。它將資料分解成多個表格,並使用外鍵建立關聯。
優點:
- 資料一致性:避免資料冗餘導致的不一致。
- 更新效率:修改資料只需要更新一個地方。
- 儲存空間節省:減少資料冗餘。
缺點:
- 查詢複雜度增加:需要 JOIN 多個表格才能取得完整的資料。
- 查詢效能下降:JOIN 操作會增加查詢成本。
反正規化的優缺點
反正規化則允許資料冗餘,將相關的資料儲存在同一個表格中。
優點:
- 查詢效能提升:減少 JOIN 操作。
- 查詢複雜度降低:更容易取得完整的資料。
缺點:
- 資料一致性降低:資料冗餘可能導致不一致。
- 更新效率下降:修改資料需要更新多個地方。
- 儲存空間增加:增加資料冗餘。
什麼時候該為了效能犧牲正規化
在高流量系統中,查詢效能至關重要。如果某些查詢非常頻繁,且 JOIN 操作的成本很高,可以考慮反正規化。
例如,假設我們有一個 orders 表格和一個 customers 表格。
|
|
如果我們經常需要查詢訂單的客戶名稱,可以將客戶名稱儲存在 orders 表格中,進行反正規化。
|
|
這樣就可以避免 JOIN 操作,提高查詢效能。但需要注意的是,在更新客戶名稱時,需要同時更新 orders 表格中的 customer_name 欄位,以保持資料一致性。
N+1 問題與批次查詢
什麼是 N+1 問題
N+1 問題是 ORM 使用者最常踩的坑之一。它指的是在查詢一個父物件時,需要額外執行 N 次查詢才能取得其關聯的子物件。
例如,假設我們有一個 posts 表格和一個 comments 表格。每個 post 可以有多個 comment。
|
|
使用 ORM 查詢所有 post 及其對應的 comment,可能會產生 N+1 問題。
產生 N+1 查詢的 ORM 寫法 (Python Django)
|
|
這段程式碼會先執行一次查詢取得所有 post,然後針對每個 post 執行一次查詢取得其 comment。如果 post 的數量是 N,總共會執行 N+1 次查詢。
使用 eager loading 優化後的版本 (Python Django)
|
|
這段程式碼使用 prefetch_related 進行 eager loading,在第一次查詢時就將所有 post 的 comment 一併取得。這樣只需要執行兩次查詢,一次取得所有 post,一次取得所有 comment。
使用 raw SQL 優化 (Python Django)
有時候,ORM 的優化仍然不夠,需要使用 raw SQL 才能達到最佳效能。
|
|
這段程式碼使用 raw SQL 執行一個 JOIN 查詢,直接取得每個 post 的 comment 數量。只需要執行一次查詢,效能最高。
查詢效能分析:EXPLAIN
理解查詢在底層如何執行,是優化效能的關鍵。可以使用 EXPLAIN 指令來分析 SQL 查詢的執行計畫。
|
|
EXPLAIN 會顯示查詢使用的索引、掃描的資料列數、JOIN 的方式等等。透過分析 EXPLAIN 的結果,可以找出效能瓶頸,並進行優化。例如,如果發現查詢沒有使用索引,可以考慮建立索引。如果發現 JOIN 操作的成本很高,可以考慮反正規化。
總結
資料庫效能調優是一個複雜的課題,需要深入理解資料庫的原理和特性。本文介紹了索引、正規化/反正規化、N+1 問題等常見的效能問題,並提供了具體的解決方案。希望這些知識能幫助你擺脫「索引大法好」的迷思,真正掌握後端工程師的基本功,寫出高效能的程式碼。記住,資料庫不是黑盒子,理解你的查詢在底層如何執行,是後端工程師從 Junior 晉升的關鍵門檻。