索引是拿寫入換查詢,寫多讀少的表加索引是純虧

「效能不好?加個索引吧」是反射動作,但索引不是免費的加速器,它是一筆交易:用寫入的成本,換查詢的成本。

每插入或更新一列,資料庫除了寫那一列,還要更新每一個相關索引。三個索引就是三棵樹要維護。所以:

表的形狀加索引
讀多寫少(一般業務表)✅ 划算
寫多讀少(log、事件流、IoT 讀數)❌ 純虧——付了寫入成本,換來的查詢加速根本沒人用

典型現場:每天塞 500 萬筆感測器讀數的表,查詢一個月才跑幾次。這種表該做的是減少索引,不是增加。

和「索引決定掃描量」不衝突

WHERE 決定回傳幾列,索引才決定要看過幾列 講的是讀的那一側:沒有索引,掃描量就是整張表。那篇成立,這篇也成立——兩篇合起來才是完整的帳:

索引的收益:查詢時少看幾列       ← 只有真的有人查才兌現
索引的成本:每次寫入多維護幾棵樹  ← 每一筆寫入都付

收益是有條件的,成本是無條件的。

而在 PostgreSQL,這個成本還要再乘一個倍數:連「沒改到索引欄位」的 UPDATE 都可能讓整張表的所有索引重寫一次,因為資料搬了位置 → PG 的寫入放大來自位置過期,不是欄位變動。 所以判準不是「這個欄位會不會被查」,而是**「查詢的頻率,值不值得每一筆寫入都付這個稅」**。

寫入為主的表,該做的是別的

痛點處方
寫入吞吐不夠減少索引、批次寫入、按時間分區
磁碟裝不下冷熱分層、時間分區歸檔
偶爾要分析送去 columnar/OLAP,見 Columnar 不是 NoSQL,是換了儲存佈局的關聯式資料庫

這三件事的區分見 「資料庫撐不住」不是一個問題,是三個。

讀那側也未必划算:低選擇性欄位

上面算的是寫入的帳。但即使你願意付那個成本,索引也可能根本不會被用到——

如果大多數列的值都一樣,即使有索引,資料庫仍然會選全表掃描。

等值查詢打在低基數欄位(布林、狀態碼、年齡)上通常沒有選擇性,撈出一大半的列再回表,比直接掃一遍還貴。處方不是「別建」,是換一種建法:

情況建法
資料高度傾斜(極少數列符合)partial index:CREATE INDEX ON t (id) WHERE active;
單獨不夠選擇性,但常和別的條件一起查複合索引:CREATE INDEX ON t (age, created_date);
不分大小寫/函數形式查詢函數索引,見 把欄位包進函數,WHERE 就用不到那個欄位的索引
子字串/模糊搜尋trigram:CREATE INDEX ON t USING gin (name gin_trgm_ops);

所以完整的判準是兩個條件要同時成立:

寫入夠少(成本付得起),而且述詞夠有選擇性(收益兌現得了)。

兩個條件都過了之後,這筆交易到底划不划算還有四個維度要看(讀寫比、數量級落差、連線被霸佔的風險、已有幾個索引)——見 建索引的代價是毫秒,收益可能是秒。

怎麼確認第二個條件成立?看執行計畫裡的 Rows Removed by Filter,以及 估計和實際差很多,代表資料庫對這張表的認識壞了——在猜測壞掉的情況下,這個判斷做不了。