Index 絕對是讓資料庫變快的超棒工具,但就像大家常說的:「太多反而不好」。不是每個 index 都有用,index 太多反而會拖累系統,幫倒忙。聽起來很矛盾對吧?我們來細細聊聊。
想像一下有個超大的圖書館,裡面有很多目錄可以找書——比如依作者、依類型、依出版年份。每個目錄都能幫你快點找到書。但如果目錄太多——像是每個書名的每個字、每個細節都做一個目錄——你會發現根本找不到東西:查找變慢、目錄佔空間,圖書館員還要一直更新這些清單,超累。
資料庫裡的 index 其實也差不多:它們幫你快點找到資料,但如果 index 太多,每次新增或修改資料時都要更新一堆 index,超麻煩。而且硬碟空間也會被吃爆。更慘的是,index 太多時,系統還會搞不清楚該用哪一個。
所以,跟圖書館的目錄一樣,index 真的不要亂加——有幾個夠用又有效的就好,沒必要搞一堆沒用的。
來玩個「PostgreSQL 偵探」遊戲。假設你對同一個欄位加了三個 index,因為你覺得這樣會變快。但想像一下:
- 如果你的 table 是一個超大的學生名單,然後有三個 index,每加一個新學生就要更新三次 index。這哪裡快了?
- 如果你有 10 個這種 table,每個都塞滿 index?整個資料庫效能直接 GG。
怎麼判斷你有沒有過度索引的問題?
第一步,先看看你現在有多少 index。在 PostgreSQL 裡可以用這個指令:
\d 表格名稱
這個指令會顯示 table、欄位還有相關的 index。如果你看到一個 table 綁了一堆 index,這就該警覺了。
另一個超好用的工具是系統視圖 pg_stat_user_indexes。它會顯示每個 index 被用過幾次,讓你知道哪些 index 根本是「死重」:
SELECT
relname AS table_name,
indexrelname AS index_name,
idx_scan AS index_scans
FROM
pg_stat_user_indexes
WHERE
idx_scan = 0;
如果 idx_scan 是 0,代表這個 index 從來沒被用過。這種 index 就是刪掉的好對象。
過度索引的範例
假設有一個 users 的 table:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE,
username VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW()
);
然後我們有三個 index:
-- email 的 index
CREATE INDEX idx_users_email ON users (email);
-- username 的 index
CREATE INDEX idx_users_username ON users (username);
-- created_at 的 index
CREATE INDEX idx_users_created_at ON users (created_at);
現在來看一下我們常做的查詢:
- 用 email 找 user。
- 用 username 找 user。
- 用
created_at排序 user。
看起來這些 index 很有用。但問題來了:如果這些查詢很少發生(比如一週才一次),那這些 index 根本沒必要。更慘的是,如果有些 index 根本沒被用過,它們只會拖慢 insert 跟 update。
舉例來說:假設 users table 裡有這些資料:
| user_id | username | created_at | |
|---|---|---|---|
| 1 | alex.lin@mail.com | alexlin | 2024-06-15 10:23:00 |
| 2 | anna.min@mail.com | annamin | 2024-06-16 12:47:00 |
| 3 | otto.song@mail.com | ottosong | 2024-06-17 08:30:00 |
| 4 | maria.chi@mail.com | mariachi | 2024-06-18 14:10:00 |
如果幾乎沒有人用 username 查詢,idx_users_username 這個 index 就完全沒被用過(idx_scan = 0),直接砍掉會更優化。
所以說,index 是很棒的工具,但要用得聰明。幾個有用又常用的 index 比一堆沒用的好多了。
怎麼避免過度索引
- 分析 index 的使用情況。 就像剛剛說的,用
pg_stat_user_indexes看看 index 的使用統計。如果某個 index 幾乎沒被用過,直接砍掉:
DROP INDEX IF EXISTS index_名稱;
- 只對常用查詢加 index。 加 index 前先問自己幾個問題:
- 這個欄位常常出現在
WHERE、ORDER BY、GROUP BY嗎? - table 裡的資料量很大嗎?
- 沒加 index 查詢真的很慢嗎?
只要有一題答案是「不是」,那這個 index 可能就多餘了。
- 用複合 index。 如果你常常在一個查詢裡用到多個欄位,不要每個欄位都加一個 index,直接做一個複合 index:
CREATE INDEX idx_users_email_username ON users (email, username);
這樣查詢同時用 email 和 username 就會快很多。
- 定期檢查現有的 index。 資料庫長大後,你的查詢可能會變。以前有用的 index,現在可能沒用了。記得定期檢查,把沒用的 index 清掉。
最小化 index 的範例
回到我們的 users table。與其三個 index,不如這樣優化:
- 如果很少用
created_at排序,就不要加這個 index。 - 把
email跟username的 index 合併成一個複合 index:
CREATE INDEX idx_users_email_username ON users (email, username);
總結:平衡的祕訣是什麼?
就像寫 code 一樣,這裡也要走極簡風:「越少越好」。不要因為可以就每個欄位都加 index。想清楚你為什麼需要這個 index,它真的能讓查詢變快嗎?要務實一點,記住:厲害的工程師不是亂加 index,而是懂得怎麼用、什麼時候用。
現在你有這個工具,就能避免過度索引的災難,讓你的資料庫快得像獵豹,不會像背著一堆沒用 index 的烏龜一樣慢吞吞。
GO TO FULL VERSION