
80億資料500行的搜尋指令是在工作時遇過得真實場景,這個系列是我在工作實務中歸納出來的學習筆記,起初真的是頭大,找資料找到眼花撩亂,希望可以給需要的人一些頭緒,這系列目前暫定先寫到這裡囉,歡迎大家交流。
第一|Execution Plan 的閱讀方向與指標
打開執行計畫,掌握兩個核心法則:
- 閱讀方向從右到左:最右邊是資料來源(表或索引),中間是各處理步驟,最左邊才是 SELECT 輸出。下次先看最右端,而非盯著 SELECT。
- 箭頭粗細代表資料量:「粗進細出」是漏斗效應,代表浪費大量資源過濾資料,需從源頭篩選;「一路粗到底」則要問應用端是否真的需要這麼多筆。
第二|看懂 Index Seek、Scan 與 Key Lookup
資料庫最大的效能瓶頸在磁碟 I/O,「精準尋找」還是「盲目翻找」差距可達百倍。
- Index Seek:最理想,利用 B-Tree 直接定位,如查字典靠索引翻到正確頁面。
- Index Scan:有索引但整個掃一遍,常因欄位套函數、型別轉換等導致索引失效,需改寫條件或調整複合索引。
- Table Scan / Clustered Index Scan:最差狀況,整張表逐筆掃描,優先建立索引或執行 UPDATE STATISTICS。
- Key Lookup:找到資料後還要「回表」撈其他欄位,大量額外 I/O。解法是把常查欄位加入索引的 INCLUDE 清單,形成 Covering Index。
第三|三大 Join 運算子解密
Optimizer 會自動選擇,但看懂背後邏輯才能抓出問題。
- Nested Loops:小表 Join 大表且有索引時效率極好;兩表都大又無索引時複雜度 O(N×M),效能急速惡化。
- Hash Match:無索引大數據的最後防線,複雜度 O(N+M),但記憶體不足時會 Spill to TempDB(出現黃色警告),是必須優先解決的瓶頸。
- Merge Join:兩表都已排序時效率最佳、記憶體需求最低;若資料未排序,SQL Server 會強加一個昂貴的 Sort,反而得不償失。
統計資料過舊會導致 Optimizer 猜錯資料量,選錯 Join 方式,執行 UPDATE STATISTICS 往往立竿見影。
第四|四個常見陷阱解析
- Missing Index:篩選欄位沒有索引,導致全表掃描。執行計畫會出現綠色提示;建立時善用 INCLUDE 可同時消滅 Key Lookup。
- Implicit Conversion:參數型別與欄位不符,SQL Server 悄悄轉換,Index Seek 退化為 Scan,只剩一個小黃警告 CONVERT_IMPLICIT 極易忽略。ORM 框架的 nvarchar/varchar 混用是最常見來源。
- Spill to TempDB:Sort、Hash Match 等運算的工作記憶體不足,中間資料溢到硬碟。根本原因通常是統計資料過舊,建索引無法解決,要跑 UPDATE STATISTICS。
- Residual Predicate:Index Seek 只用了部分鍵欄位定位,其餘條件逐列過濾,Rows Read 遠大於 Actual Rows 卻不易察覺。需點開節點屬性分辨 Seek Predicates 與 Predicates,調整複合索引欄位順序。
第五|除錯 SOP 四步驟
面對幾十上百節點的龐大執行計畫,核心心法:一次只動一個地方,永遠以數據驗證差異。
- Step 1 擒賊先擒王:找 Cost % 最高的節點與最粗的箭頭,鎖定 1~2 個最差局部,不急著全域改寫。
- Step 2 懸停健康檢查:看有無黃色驚嘆號、存取方式是 Seek 還是 Scan、有無伴隨 Key Lookup。
- Step 3 戳破預估幻覺:比對 Estimated Rows 與 Actual Rows,落差大先跑 UPDATE STATISTICS,往往 Optimizer 就能自動選對計畫。
- Step 4 單一變數驗證:改前用 SET STATISTICS IO ON 記錄基準,每次只改一件事,確認 Logical reads 下降才算真正有效。
Read More
感謝閱讀,為了方便日後系統化檢索,我將技術筆記同步收錄在個人知識基地愷的大冒險 Kai's Adventure