☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
sql 進階

SQL 效能調校

為什麼要調校?

比喻:效能調校就像交通規劃 🚗

一條路(查詢)如果有 100 萬台車(資料), 沒有紅綠燈和分流(索引和優化),就會塞車(查詢很慢)。


EXPLAIN ANALYZE — 查詢計畫分析

-- 查看查詢的執行計畫
EXPLAIN ANALYZE
SELECT * FROM Students WHERE Email = 'test@gmail.com';
-- 沒有索引時:
Seq Scan on students            ← 全表掃描!
  Filter: (email = 'test@gmail.com')
  Rows Removed by Filter: 99999
  Planning Time: 0.1 ms
  Execution Time: 150.5 ms      ← 很慢

-- 有索引後:
Index Scan using idx_email      ← 用索引!
  Index Cond: (email = 'test@gmail.com')
  Planning Time: 0.1 ms
  Execution Time: 0.05 ms       ← 超快

關鍵字解讀

術語 意思 好壞
Seq Scan 全表掃描 ❌ 慢
Index Scan 使用索引 ✅ 快
Bitmap Index Scan 點陣圖索引 ✅ 快
Nested Loop 巢狀迴圈 JOIN 小表 OK
Hash Join 雜湊 JOIN 大表 OK
Sort 排序 注意記憶體

常見效能陷阱

1. SELECT * 的問題

-- ❌ 壞:取全部欄位(包含大型 TEXT 欄位)
SELECT * FROM Articles WHERE CategoryId = 5;

-- ✅ 好:只取需要的欄位
SELECT Id, Title, CreatedAt FROM Articles WHERE CategoryId = 5;

2. 在索引欄位上做運算

-- ❌ 壞:函數包住欄位 → 索引失效
SELECT * FROM Students WHERE UPPER(Name) = 'MIKE';
SELECT * FROM Orders WHERE YEAR(OrderDate) = 2024;

-- ✅ 好:避免在欄位上做運算
SELECT * FROM Students WHERE Name = 'Mike';  -- 用大小寫不敏感的比對
SELECT * FROM Orders
WHERE OrderDate >= '2024-01-01' AND OrderDate < '2025-01-01';

3. LIKE 開頭用萬用字元

-- ❌ 壞:開頭用 % → 索引失效
SELECT * FROM Students WHERE Name LIKE '%明';

-- ✅ 好:開頭用確定值
SELECT * FROM Students WHERE Name LIKE '小%';

4. OR 可能導致索引失效

-- ❌ 可能不用索引
SELECT * FROM Students WHERE Age = 20 OR Email = 'test@gmail.com';

-- ✅ 改用 UNION
SELECT * FROM Students WHERE Age = 20
UNION
SELECT * FROM Students WHERE Email = 'test@gmail.com';

N+1 查詢問題

-- ❌ N+1 問題(應用程式層常見)
-- 1. 先查所有訂單(1 次查詢)
SELECT * FROM Orders;
-- 2. 對每筆訂單查客戶名稱(N 次查詢)
SELECT Name FROM Customers WHERE Id = 1;
SELECT Name FROM Customers WHERE Id = 2;
-- ... 重複 N 次

-- ✅ 用 JOIN 一次搞定
SELECT o.*, c.Name AS CustomerName
FROM Orders o
INNER JOIN Customers c ON o.CustomerId = c.Id;

分頁優化

-- ❌ 大 OFFSET 很慢(掃描 + 丟棄前 100000 筆)
SELECT * FROM Orders
ORDER BY Id
LIMIT 10 OFFSET 100000;

-- ✅ 用游標分頁(Keyset Pagination)
SELECT * FROM Orders
WHERE Id > 100000              -- ← 直接從上一頁最後的 Id 開始
ORDER BY Id
LIMIT 10;

其他優化技巧

-- 1. 用 EXISTS 取代 IN(大子查詢時)
-- ❌
SELECT * FROM Students WHERE Id IN (SELECT StudentId FROM Enrollments);
-- ✅
SELECT * FROM Students s WHERE EXISTS (
    SELECT 1 FROM Enrollments e WHERE e.StudentId = s.Id
);

-- 2. 批次操作取代逐筆操作
-- ❌ 一筆一筆插入
INSERT INTO Logs VALUES (...);
INSERT INTO Logs VALUES (...);
-- ✅ 批次插入
INSERT INTO Logs VALUES (...), (...), (...), ...;

-- 3. 適當使用 Materialized View
CREATE MATERIALIZED VIEW mv_report AS
SELECT ... -- 複雜的報表查詢
;
REFRESH MATERIALIZED VIEW mv_report;  -- 定時刷新

效能檢查清單

# 檢查項目 做法
1 是否有用到索引? EXPLAIN ANALYZE
2 SELECT * ? 改成只選需要的欄位
3 WHERE 欄位有運算? 移除運算,改寫條件
4 N+1 查詢? 改用 JOIN
5 大 OFFSET? 改用 Keyset Pagination
6 適合的索引? 檢查 WHERE、JOIN、ORDER BY 的欄位

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊