接著上一張 N+1 講。N+1 是「同一件事問了 101 次」;這篇要講的是另一種慢——你只問了一次,但這一次就跑超久。
情境長這樣:一個用 email 找使用者的查詢,WHERE email = ?,簡單到不行。開發的時候秒回,上線幾個月、使用者表長到幾十萬筆之後,同一句查詢開始要跑好幾秒。SQL 沒改、邏輯沒改,就只是資料變多了。
差別在哪?多數時候,就差一個 index。
先用「翻書」搞懂它
想像你手上一本沒有目錄的字典,我要你找「跩」這個字。你只能怎麼辦?從第一頁開始一頁一頁翻,翻到為止。這就是 DB 沒有 index 時做的事——叫全表掃描(Seq Scan),為了找符合條件的那幾筆,它把整張表從頭到尾翻一遍。
現在這本字典有目錄了。你要找「跩」,先翻目錄查到它在第 387 頁,直接跳過去。你不用翻過前面 386 頁。 這就是 index。
index 底層通常是一種叫 B+ tree 的結構,但你不用記它的實作細節,只要抓住那個直覺就好:它讓「找一筆」從「翻完整本」變成「查目錄跳過去」。 用 CS 的話講,是從 O(n)(資料幾筆就翻幾筆)變成 O(log n)(翻個幾層就到)。
所以到底差多少
關鍵就在這個「線性 vs 對數」的差距,而且資料越多,差距越誇張:
- 表裡只有 10 筆:有沒有 index 根本沒差,DB 一眼掃完,你感覺不到。
- 表裡有 100 萬筆:沒 index 它要平均翻 50 萬筆才找到;有 index(B+ tree)大概翻 20 層內就到。
這不是「快一點」,是差好幾個數量級。而且這正是為什麼 index 問題跟 N+1 一樣——開發時完全沒感覺,資料長大才爆。你 dev 環境塞 10 筆假資料,Seq Scan 跟 Index Scan 都是一瞬間,你怎麼可能發現?等 production 資料堆到幾十萬、那個沒 index 的 WHERE 才會原形畢露。
所以「差多少」沒有一個固定數字——它跟你的資料量直接掛鉤。資料越大,那個沒加的 index 就讓你賠得越慘。
重點:怎麼自己驗有沒有走 index
跟 N+1 一樣,你不能用「猜」的,要用看的。資料庫給了你一個 X 光機——EXPLAIN。
把它擺在你的查詢前面,DB 會告訴你它打算怎麼執行這條 query(不會真的跑);想連實際耗時也一起看,就用 EXPLAIN ANALYZE(這個會真的跑一次):
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = '[email protected]';你要在輸出裡找的,就一個關鍵字:
| 你看到 | 意思 | 該怎樣 |
|---|---|---|
| Seq Scan(全表掃描) | DB 正在「一頁一頁翻整張表」 | ⚠️ 如果這張表很大,這就是你的兇手——該加 index 了 |
| Index Scan(索引掃描) | DB 有走 index,直接跳到目標 | ✅ 這就是你要的 |
再順手看一下它估的 rows——如果一個「只想撈一筆」的查詢,DB 卻說它要掃幾十萬 rows,那幾乎可以確定它在做全表掃描。看到大表上的 Seq Scan,就是該加 index 的信號。
加法本身很單純:
CREATE INDEX idx_users_email ON users (email);加完再 EXPLAIN 一次,看它從 Seq Scan 變成 Index Scan,你就親眼驗證了這個 index 有效。
但 index 不是加越多越好
看到這你可能想「那我每個欄位都加 index 不就天下無敵」——別,這又是另一個坑:
- 寫入會變慢:每次 insert / update,DB 都得順便維護所有相關的 index。你加越多 index,寫入就被拖越慢。index 是拿「寫的成本」換「讀的速度」。
- 複合索引有順序講究:
(a, b)的索引能幫WHERE a跟WHERE a AND b,但幫不了只查WHERE b的——這叫最左前綴原則。 - 有些寫法天生用不到 index:最經典的就是
LIKE '%關鍵字'(開頭是萬用字元),目錄幫不了你,只能乖乖全表掃。
這些更深的設計取捨,留給專門的索引文章:
個人經驗:我自己的經驗其實超單純——某張表查詢慢,加了 index 之後速度「咻」一下就上去了,就這樣。但老實講,我以前都是憑感覺加:覺得這個欄位常被查就加個 index,加完看頁面「好像有變快」就收工,從來沒真的用 EXPLAIN 去驗過它到底有沒有走 index。
寫這篇我才意識到中間有個洞——「感覺有變快」跟「DB 真的用了那個 index」是兩回事。你可能加了一個根本沒被用到的 index(白白拖慢寫入),也可能那次變快其實是別的原因。EXPLAIN 就是把「感覺」換成「親眼看到 Seq Scan 變成 Index Scan」。所以如果你跟過去的我一樣靠感覺加 index——這篇就是想說服你,多花那 10 秒照一下,真的值得。
接下來往哪走
這篇想給你的是:看到查詢變慢,先懷疑 index、而且會自己用 EXPLAIN 驗。再深的:
- EXPLAIN 各種節點怎麼讀(Nested Loop / Hash Join…) → 本章
29-explain-analyze(規劃中) - 什麼時候該加、複合索引怎麼排、怎麼查沒用到的 index → 本章
28/31(規劃中) - 另一種慢法——查詢打太多次(N+1) → ORM 怎麼偷偷害你
- 既有實戰:Query Optimization、壓測前後的 DB 批次與索引改造
反思
index 的觀念其實樸素到不行——給你的表一個目錄,DB 就不用每次都翻完整本書。 難的不是理解它,是記得去檢查。
因為它跟 N+1 是同一種陰險:dev 環境永遠看不出來,等 production 資料夠大才咬你一口。
所以每次寫一個「會用 WHERE / JOIN / ORDER BY 去撈大表」的查詢,養成一個動作就好——上線前,拿 EXPLAIN 照它一下:
- 它走的是 Index Scan 還是 Seq Scan?
- 如果是 Seq Scan,這張表將來會長到多大?小表無所謂,大表就是埋雷。
照一下花你 10 秒,但能讓你少一個「幾個月後莫名其妙變慢、還查不出原因」的半夜。