編寫高性能 SQL 的終極目標只有三個:
三者往往相輔相成:少 I/O → 少 CPU 處理數據 → 快響應。
當優化器預估索引掃描需訪問的數據頁 ≥ 全表掃描頁數時(通常超過表行數的 20%-30%),全表掃描反而更快。不要強制索引,讓基於成本的優化器(CBO)根據統計信息決策。
IN (SELECT ...) → JOIN;EXISTS 通常比 IN 更穩定;衍生表物化(MySQL 5.6+)可緩解部分問題。-- ❌ NOT IN:若子查詢返回 NULL,整個結果為空(三值邏輯陷阱)
SELECT * FROM t1 WHERE id NOT IN (SELECT t1_id FROM t2);
-- ✅ NOT EXISTS:語義清晰,無 NULL 陷阱,優化器易轉為 Anti-Join
SELECT * FROM t1 WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t2.t1_id = t1.id);
-- ✅ 走索引(前綴匹配)
WHERE name LIKE 'Zhang%'
-- ❌ 全表掃描(通配符開頭)
WHERE name LIKE '%Zhang%'
-- ❌ 函數殺死索引
WHERE SUBSTR(name, 1, 5) = 'Zhang'
-- Oracle 8i+ 可建函數索引修復,MySQL 8.0 支持函數索引
| 反模式 | 優化方向 |
|---|---|
SELECT * | 顯式列名,減少網絡傳輸、覆蓋索引機會增大 |
OR 連接不同列 | 拆分為 UNION ALL,各走各的索引 |
| 隱式類型轉換 | 字符列加引號,數字列不加 |
| 大事務長鎖定 | 拆分小批次提交,縮短鎖持有時間 |
隨機排序 ORDER BY RAND() | 應用層隨機 ID 或 WHERE id >= (SELECT FLOOR(RAND() * MAX(id)) FROM t) LIMIT 1 |
EXPLAIN FORMAT=JSON SELECT ...:查看 cost_info、used_columns、attached_condition。SHOW PROFILE FOR QUERY query_id;:細粒度 CPU/IO/鎖/網絡耗時(需啟用 profiling)。OPTIMIZER_TRACE:MySQL 5.6+ 追蹤優化器決策全過程。long_query_time=0 + log_queries_not_using_indexes)配合 pt-query-digest 分析 Top SQL。SQL 優化無銀彈,但遵循「少掃描、少交互、少計算、善用索引、避開陷阱」五字訣,配合持續的慢查詢審計與 EXPLAIN 驗證,即可解決 90% 性能問題。記住:最好的優化是不寫爛 SQL,其次是寫對索引,最後才是調參數。