把欄位包進函數,WHERE 就用不到那個欄位的索引
索引存的是欄位原本的值。一旦你在
WHERE裡把欄位丟進函數,資料庫要比對的就不再是索引裡那個值,只能每一列都先算一次函數再比——索引直接出局。
WHERE COALESCE(bonus, 0) < 1000 -- ❌ bonus 的索引用不到
WHERE bonus < 1000 OR bonus IS NULL -- ✅ 用得到
WHERE DATE(created_at) = '2026-09-20' -- ❌
WHERE created_at >= '2026-09-20' AND created_at < '2026-09-21' -- ✅
WHERE UPPER(email) = 'A@B.COM' -- ❌
WHERE email = 'a@b.com' -- ✅(改用 citext 或函數索引)判準很簡單:欄位必須光禿禿地站在比較運算子的一邊。 這個性質有個名字叫 sargable(Search ARGument ABLE)。
更精確的講法是看哪一邊被包:
轉型發生在「欄位」上會出事,發生在「常數」上沒關係。
WHERE height::text = '1.56' -- ❌ 欄位被轉型 → numeric 索引失效
WHERE height = '1.56'::numeric -- ✅ 轉的是常數 → 索引照用常數那一側你怎麼包都行——它只算一次,而且算完就是一個值,索引照樣定位得到。
為什麼這條常被忽略
因為包函數的版本通常更好讀——COALESCE(bonus, 0) < 1000 一句話講完意圖,比 bonus < 1000 OR bonus IS NULL 乾淨。可讀性和 sargability 在這裡是對立的,而可讀性的收益是常數,索引的收益隨資料量放大。
這正是 WHERE 決定回傳幾列,索引才決定要看過幾列 的延伸:WHERE 寫得再精準,決定掃描量的仍然是索引有沒有派上用場。包了函數 = 索引沒派上用場 = 回到 full scan,不管條件多窄。
真的需要的時候
如果那個表達式本身就是你的查詢模式(例如永遠用小寫 email 查),大多數資料庫可以建函數索引 / 表達式索引:
CREATE INDEX idx_email_lower ON users (LOWER(email));建了之後 WHERE LOWER(email) = ... 就變回 sargable。重點不是「不准用函數」,而是索引必須跟 WHERE 裡的表達式長得一樣。