PostgreSQL 索引優化教學:B-Tree、GIN、GiST 完整指南
如果你寫過後端,一定遇過這種情況:明明資料才幾萬筆,查詢卻慢到不行。十之八九,問題出在索引沒設好。今天我想用自己踩過的坑,帶你搞懂 PostgreSQL 索引優化的核心觀念,讓你的查詢速度直接起飛。
為什麼索引這麼重要?
想像一下,你去圖書館找一本書,如果沒有目錄、沒有分類,你只能從第一排書架一本一本翻過去。資料庫查詢也是同樣的道理——沒有索引的情況下,PostgreSQL 必須做 Sequential Scan(全表掃描),把每一筆資料都看過一遍才能回傳結果。
當資料量從幾千筆成長到幾十萬、甚至上百萬筆時,沒有索引的查詢會讓你的 API 回應時間從毫秒級暴增到好幾秒。這不只影響使用者體驗,在高併發的情境下更會拖垮整個系統。如果你正在設計 API,不妨參考REST API 設計最佳實踐這篇文章,好的 API 設計搭配正確的索引策略,才能打造出高效能的後端服務。
B-Tree 索引:最常用的預設武器
B-Tree 是 PostgreSQL 預設的索引類型,大概 90% 的場景你都會用到它。它特別適合處理等值查詢(=)和範圍查詢(<、>、BETWEEN)。
B-Tree 適用場景
- 主鍵和外鍵欄位(PostgreSQL 會自動幫主鍵建 B-Tree)
- WHERE 條件中常用的欄位
- ORDER BY 排序欄位
- JOIN 條件欄位
建立 B-Tree 索引非常簡單:
CREATE INDEX idx_users_email ON users(email);但這裡有個很多新手會犯的錯誤:不是欄位加了索引就一定會變快。如果你的查詢條件是 WHERE status = 'active',但 active 的資料佔了全表 95%,PostgreSQL 反而會選擇不用索引,因為全表掃描更划算。索引最有效的時候,是在篩選出少量資料的情境下。
複合索引的眉角
當你的查詢經常同時篩選多個欄位時,複合索引就派上用場了:
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);這裡有個關鍵原則叫做「最左前綴原則」。上面這個索引可以加速 WHERE user_id = 1 的查詢,也可以加速 WHERE user_id = 1 AND created_at > '2026-01-01' 的查詢,但無法加速單獨 WHERE created_at > '2026-01-01' 的查詢。欄位順序很重要,一定要把選擇性最高的欄位放前面。
GIN 索引:全文搜尋和 JSONB 的好夥伴
如果你有用到 PostgreSQL 的 JSONB 欄位或全文搜尋功能,GIN(Generalized Inverted Index)索引絕對是你的首選。
JSONB 欄位索引
現在很多專案會用 JSONB 來儲存半結構化資料,像是使用者偏好設定、商品屬性之類的。對 JSONB 建立 GIN 索引可以大幅加速查詢:
CREATE INDEX idx_products_attrs ON products USING gin(attributes);建立之後,像 WHERE attributes @> '{"color": "red"}' 這樣的查詢就能利用索引快速過濾。我自己在做電商專案時,加了 GIN 索引之後,商品篩選的查詢時間從 800ms 降到 15ms,差距非常誇張。
全文搜尋索引
PostgreSQL 內建的全文搜尋功能搭配 GIN 索引,很多時候可以省掉引入 Elasticsearch 的複雜度:
CREATE INDEX idx_articles_search ON articles USING gin(to_tsvector('chinese', title || ' ' || content));不過老實說,如果你的搜尋需求比較複雜,或是資料量真的很大(百萬級以上),還是建議評估專門的搜尋引擎。
GiST 索引:地理和範圍查詢的利器
GiST(Generalized Search Tree)索引比較少人知道,但在特定場景下威力驚人。它特別適合:
- 地理空間查詢(PostGIS)
- 範圍類型(Range Types)查詢
- 幾何圖形的包含、相交判斷
舉個例子,假設你在做一個附近餐廳的功能:
CREATE INDEX idx_restaurants_location ON restaurants USING gist(location);搭配 PostGIS 的 ST_DWithin 函數,就能高效查詢指定範圍內的餐廳。如果你的服務是用 Docker 部署的,可以參考Docker Compose 多容器部署指南來設定 PostGIS 的容器環境。
用 EXPLAIN ANALYZE 診斷查詢效能
設定索引之前,你得先知道問題在哪裡。EXPLAIN ANALYZE 是 PostgreSQL 最重要的效能診斷工具:
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123 AND status = 'pending';執行結果會告訴你:
- 查詢計畫是用 Seq Scan 還是 Index Scan
- 預估和實際的行數差異
- 每個步驟花費的時間
- 是否有用到你建立的索引
我的經驗是,每次寫完重要的查詢,一定要跑一次 EXPLAIN ANALYZE 確認。很多效能問題在開發環境看不出來(因為資料量小),但上了正式環境就會爆炸。
實戰優化技巧
部分索引(Partial Index)
如果你只需要索引符合特定條件的資料,部分索引可以大幅減少索引大小:
CREATE INDEX idx_orders_pending ON orders(created_at) WHERE status = 'pending';這個索引只會包含 status 為 pending 的訂單,比全表索引小很多,查詢速度也更快。
索引維護
索引不是建了就不用管。隨著資料的新增、修改、刪除,索引會產生碎片化的問題。定期執行 REINDEX 和 VACUUM 是很重要的維護工作:
REINDEX INDEX idx_users_email;
VACUUM ANALYZE users;建議把這些維護工作排進 cron job,每週至少跑一次。
避免過度索引
很多開發者會犯「索引越多越好」的錯誤。每個索引都會:
- 佔用額外的磁碟空間
- 拖慢 INSERT、UPDATE、DELETE 的速度
- 增加 VACUUM 的負擔
我的建議是:只為真正有需要的查詢建立索引,並且定期用 pg_stat_user_indexes 檢查哪些索引根本沒被使用到,該移除就移除。
總結
PostgreSQL 索引優化的關鍵不在於背誦各種索引類型的語法,而是理解你的查詢模式和資料特性,選擇最適合的索引策略。B-Tree 處理一般查詢、GIN 搞定 JSONB 和全文搜尋、GiST 負責空間查詢,三者各有擅場。
最後提醒一下,如果你在猶豫 API 架構要用 REST 還是 GraphQL,可以看看GraphQL vs REST API 完整比較這篇分析。不管選哪種架構,後端的索引優化都是不可或缺的基本功。持續用 EXPLAIN ANALYZE 監控查詢效能,你的資料庫就能穩穩地撐住業務成長。
繼續閱讀
Redis 快取策略教學:Cache-Aside、Write-Through 到實戰踩坑全紀錄
快取不是 set/get 那麼簡單。這篇從 Cache-Aside、Write-Through 到 Write-Behind,帶你理解每種策略的取捨,加上我踩過的坑,幫你少走彎路。
相關文章
你可能也喜歡
探索其他領域的精選好文