先找出真正影響業務的查詢
頁面慢不一定等於 SQL 查詢慢。請求可能在應用程式、外部服務、鎖定、取得連線,或多個各自不算慢的查詢上等待。先描述顧客可見的症狀:哪個動作慢、何時發生、涉及哪類帳戶或資料形態、頻率如何。沿應用程式時間紀錄追查至資料庫語句,記錄查詢模式、參數或參數範圍,以及相關資料表大小。不要把個人資料或秘密複製到工單。只用少量測試資料跑一條精心挑選的查詢,通常不能代表正式環境。目標是建立可在改動前後比較的工作負載,而非製作看似專業的執行計劃。
PostgreSQL 的 pg_stat_statements 模組可記錄 SQL 語句規劃及執行統計。官方文件指出,加入或移除模組需要設定 shared_preload_libraries 及重新啟動伺服器,並啟用查詢識別碼;個別資料庫亦須建立擴充功能才能使用相關檢視。因此啟用它屬於需要安排的營運變更,並非隨手開啟的儀表板。若已可使用,就在指定觀察時段比較呼叫次數、平均及最高執行時間、總耗時與資料區塊活動。中等慢但頻繁執行的查詢,可能比偶發極慢查詢耗用更多資源;罕見但有期限要求的報表仍可能更重要。每個數值都要附觀察期間和工作負載背景。
例如某訂單歷史頁只在資料累積多年的帳戶變慢。這是示範調查方法,並非實際客戶成效:擷取小帳戶與大帳戶各自使用的篩選和排序條件,並確認介面要求首頁還是較深的頁數。小帳戶的計劃可能因資料少而選擇掃描,這不代表大帳戶不需要索引。同樣地,若畫面只顯示少量紀錄,查詢卻傳回數千筆資料,縮減結果集可能比加索引更有效。先界定顧客真正要完成的事,例如查看最新相關訂單,才選資料庫的修正方法。
把執行計劃當證據,不當結論
使用 EXPLAIN 查看規劃器擬採用的操作及預估列數。EXPLAIN ANALYZE 不同:PostgreSQL 會實際執行語句,並報告實際列數和時間。官方指南提醒統計抽樣會令估算改變,而閱讀計劃亦需審慎。若語句會寫入資料,實際執行可能更改紀錄,絕不能在正式業務資料上隨便測試;應與資料庫負責人安排安全環境或受控交易。讀取查詢則要選具代表性的參數,在可接受診斷負載的時段運行。保存計劃、PostgreSQL 版本、資料庫結構及相關設定,方便日後比較。只有一張時間數字截圖並不足夠。
比較計劃各節點的預估列數與實際列數,找出首次明顯偏離的位置。若規劃器預期只有少量資料,實際卻讀到資料表大部分,過時或不足的統計資料可能比缺少索引更值得先檢查。PostgreSQL 的 ANALYZE 會收集供規劃器使用的資料表統計;適當更新可能改變所選計劃,但仍要考慮工作負載並安排時段。除掃描類型外,也要看篩選、連接及排序。需要讀取小表大部分資料時,順序掃描未必是錯;索引掃描若讀取大量分散資料再排序,也未必好。要按實際查詢及資料分佈分析,不可單憑操作名稱下判斷。
BUFFERS 選項會增加各計劃節點的緩衝區命中、讀取、變髒及寫入資訊,有助分辨主要使用快取頁面的查詢與需要把頁面載入記憶體的查詢,但命中快取不代表整條查詢一定便宜。重複測試可能受暖快取影響,與顧客在閒置後的第一次請求不同。比較計劃時要保持資料及快取條件盡量相近,並同時量度應用層延遲,因為網絡傳輸、JSON 建立及畫面呈現都不在資料庫計劃內。若問題間歇出現,還須在整體追蹤中檢查鎖定及併發;單靠索引實驗可能掩蓋真正原因。
選擇並驗證相稱的改動
當穩定的篩選或排序條件可讓 PostgreSQL 找到少量有用資料時,索引才是候選方案之一。先檢查現有索引,包括欄位次序及條件。PostgreSQL 文件指出索引可加快讀取,但會增加整個資料庫的負擔;每個新索引也需要儲存和維護成本。符合實際篩選及排序的複合索引可能有用,大量猜測性單欄索引則可能拖慢寫入而不改善該查詢。其他方法包括減少回傳欄位或列數、改寫不必要的連接、修正分頁、更新統計或改變昂貴的應用程式讀取模式。建立索引前要先寫下假設與預期計劃變化。
在正式資料表建立索引本身就是部署決定。PostgreSQL 的 CREATE INDEX 文件解釋,並行建立可避免一般索引建立方式對寫入的阻擋,但需更多工作、帶有營運限制,而且不能在交易區塊內執行。失敗後亦可能留下無效索引,須調查,不能當成已修復。排程前與操作人員確認資料庫版本及準確指令;用接近真實的資料演練,檢查磁碟空間並監察建立過程。若索引改善一條讀取路徑,卻明顯拖慢訂單寫入或耗盡儲存,即使單一 EXPLAIN 計劃變快也不能接受。
改動後,在可比較的觀察時段,以相同顧客操作及具代表性的查詢參數作比較。記錄實際執行計劃、請求延遲、呼叫量、寫入表現、儲存用量及錯誤情況。同時檢查常見情況與最難處理的一端;平均值可能掩蓋仍在等待的顧客。若規劃器不用新索引,應調查原因而非盲目強迫使用:資料表可能太小、篩選力不足、統計過時,或查詢運算式並不吻合。任何營運改動都要有回退方法,包括如何安全移除無用索引。只有業務操作真正改善且新成本已清楚,才算完成,而非遷移指令一結束便結案。
用一個示範比對可保持決定誠實。假設訂單歷史頁按帳戶篩選、由新到舊排序,並只要求有限數量的紀錄。先記錄新帳戶與多年帳戶的真實查詢形態和計劃,不要虛構效能數據。若多年帳戶的查詢先讀取大量列並排序,才回傳少量結果,可測試現有索引或審慎設計的複合索引能否支援篩選與排序;若時間主要耗在等待鎖定,針對掃描加索引便無法解決。若應用程式先取回全部歷史再自行切頁,應先修正這個界線。每個方案都要寫下預期對讀取列數、排序工作、寫入維護及儲存的影響,之後以相同個案量度。這樣才不會用一張好看的計劃截圖取代真實顧客結果,也可揭示單一查詢時間掩蓋的取捨。
調校前先訂停止規則。由業務方選定顧客操作及可接受的回應目標,再找出查詢在總時間中的比例。若請求主要耗在畫面呈現或外部服務,資料庫改動只縮短小部分時間,就應停止不斷加索引,轉查較大延誤。相反,若改善了常見情況卻令大帳戶更慢,難處理的個案仍應是發佈前須解決的問題。比較相等的流量時段,並解釋資料量、硬件、快取狀態或應用版本的變動。平均執行時間下降,不能直接說每位顧客都有改善。只有顧客結果、營運成本及回退準備均支持改動,決定才算完成;執行計劃是判斷證據,不是最終目標。
正式更改系統前
- 能否把顧客感受到的緩慢操作連到具代表性的 SQL 語句?
- 執行計劃有否指出首次重大估算或工作量差異?
- 建議修正能否改善業務操作,同時避免不可接受的寫入成本?


