CodeGym /課程 /SQL SELF /使用索引時常見的問題

使用索引時常見的問題

SQL SELF
等級 38 , 課堂 4
開放

就算是最先進的車子,如果你加的是檸檬汽水而不是汽油,也會開不動。索引在 PostgreSQL 也是這樣,雖然很強,但要會用才行。來看看幾個跟索引有關的常見問題吧。

問題 1:索引太多

先複習一下前幾堂課的內容。如果你在一個表上加太多索引,PostgreSQL 每次都得維護這些索引,才能讓它們保持最新。這會直接影響到寫入、更新和刪除操作。因為每個索引都要更新、同步!

假設我們有個 students 表:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255) UNIQUE,
    age INTEGER,
    grade INTEGER
);

然後你決定「以防萬一」每個欄位都加個索引:

CREATE INDEX idx_students_name ON students(name);
CREATE INDEX idx_students_age ON students(age);
CREATE INDEX idx_students_grade ON students(grade);

現在想像你要插入一萬筆新資料。PostgreSQL 不只要把資料寫進表裡,還要更新這三個索引。如果資料量很大,寫入速度就會變慢,整個系統效能也會掉下來。

怎麼避免這種情況? 在建立索引前,先問自己兩個問題:

  1. 這個欄位有多常被用在篩選WHERE)、排序ORDER BY)或分組GROUP BY)?
  2. 查詢真的會用到這個索引,還是最後還是會做全表掃描

如果這兩題的答案都是「很少」或「幾乎不會」,那這個索引就沒必要。

問題 2:選錯欄位來建索引

在變化很少的資料上建索引,就像想把茶倒進已經封死的杯子裡:幾乎沒什麼用。如果一個欄位只有兩三個不同的值,PostgreSQL 很可能還是會直接全表掃描,不會用索引。

假設我們有個 courses 表:

CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    level VARCHAR(10) -- 只能是 'Beginner'、'Intermediate' 或 'Advanced'
);

然後你決定在 level 欄位建索引:

CREATE INDEX idx_courses_level ON courses(level);

但這個查詢:

SELECT * FROM courses WHERE level = 'Beginner';

可能不會用到索引,因為 PostgreSQL 會覺得直接掃整個表比較快,根本不用查索引。這在小表和變化很少的資料特別明顯。

所以索引比較適合用在高基數欄位(就是有很多不同值的欄位)。如果資料變化很少,建議用其他優化方法,比如表分割(partitioning)。

問題 3:過時的索引

有時候索引建了,後來卻忘記刪掉,即使已經沒人在用。就像桌面上的檔案,一開始只有兩三個,後來一堆,找東西還要花時間一個個看過去...是不是很熟悉?

假設我們為舊功能建了一個索引,後來查詢邏輯改了,又加了新索引。舊的索引沒人用,卻還佔空間、拖慢寫入。

為了避免這種情況,要定期檢查和分析索引。PostgreSQL 有個很方便的指標:

SELECT
    relname AS table_name,
    indexrelname AS index_name,
    idx_scan AS total_scans
FROM
    pg_stat_user_indexes
WHERE
    idx_scan = 0;

這裡 idx_scan 代表這個索引被查詢用過幾次。如果是 0,就表示沒人在用,可以刪掉:

DROP INDEX idx_courses_level;

問題 4:常常被更新的欄位建索引

如果你在一個經常被更新的欄位上建索引,PostgreSQL 每次改動都要重建索引,這會大幅拖慢效能。

想像有個訂單資料表:

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    status VARCHAR(20), -- 可能會改很多次(像是 "新訂單"、"處理中"、"已完成")
    total NUMERIC(10, 2)
);

你為 status 欄位建了索引,想讓狀態查詢更快:

CREATE INDEX idx_orders_status ON orders(status);

但如果每筆資料的狀態都會改很多次,這個索引反而會拖慢效能。

要避免這種情況,別在常常變動的欄位建索引。如果真的需要索引,可以考慮用部分索引

CREATE INDEX idx_orders_status_partial
ON orders(status) 
WHERE status = '處理中';

這樣只有指定狀態的資料才會更新索引。

問題 5:在不必要的欄位加 UNIQUE 限制

唯一索引(UNIQUE)會自動建立來保證資料唯一。但如果沒必要唯一,這些索引只會增加負擔。

假設我們有個 log 表:

CREATE TABLE logs (
    id SERIAL PRIMARY KEY,
    message TEXT,
    created_at TIMESTAMP UNIQUE
);

如果每秒都插入上千筆資料,created_at 的唯一性會造成很大壓力。

要一切順利,只有真的需要唯一時才加 UNIQUE 限制。如果 created_at 不一定要唯一,可以改成一般索引:

CREATE INDEX idx_logs_created_at ON logs(created_at);

問題 6:錯誤使用複合索引

複合索引(multi-column indexes)很有用,當查詢會同時用到多個欄位來篩選或排序。但這種索引要建對,不然根本不會被用到。

假設我們有這個索引:

CREATE INDEX idx_students_name_grade ON students(name, grade);

這個索引會在查詢同時用到兩個欄位時被用到:

SELECT * FROM students WHERE name = 'Alice' AND grade = 90;

但這個查詢:

SELECT * FROM students WHERE grade = 90;

就不會用到這個索引,因為 name 是第一個欄位。

為了避免這種問題,複合索引要按照查詢最常用的順序來建。如果只會用到其中一個欄位,建議另外建單獨的索引。

實用小建議

監控索引使用情況。PostgreSQL 有個系統視圖 pg_stat_user_indexes,可以看到哪些索引有被用,哪些沒被用。

查詢和索引一起優化。爛查詢加索引還是爛查詢。

記得刪除不用的索引。過時的索引只會佔空間、拖慢寫入。

就這樣啦,朋友們!索引真的很強,但記住「能力越大,責任越大」。用索引要有意識,這樣你的資料庫就會像 SpaceX 火箭一樣衝超快!

1
問卷/小測驗
過度索引的問題,等級 38,課堂 4
未開放
過度索引的問題
過度索引的問題
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION