你在一個有 10 筆資料的表格裡找某個名字,從第一列看到最後一列通常沒什麼問題。但如果那張表不是 10 筆,而是 1,000 萬筆呢?
假設有一張 users 表:
id | name | email
1 | Amy | amy@example.com
2 | Ben | ben@example.com
3 | Chen | chen@example.com
...
現在執行:
SELECT * FROM users WHERE email = 'chen@example.com';
人看到這行 SQL,會覺得資料庫只是「找出 email 等於這個值的人」。但真正重要的問題是:資料庫要怎麼找?
如果 email 沒有適合的索引,一種最直接的方法就是從第一筆一路檢查到最後一筆。這叫做 Full Table Scan,也就是全表掃描。
資料只有幾百筆時,你幾乎感覺不到差異;資料成長到幾百萬、幾千萬筆時,同樣一句 SQL 的成本就可能完全不同。
Index 到底是什麼?
Database Index 可以理解成「為了快速尋找資料而額外建立的資料結構」。
最常見的比喻是書本後面的索引。假設一本 800 頁的教科書裡出現很多次「database」,你當然可以從第一頁開始翻,但更有效率的方法是先查看索引,知道這個詞出現在哪些頁,再直接翻到那些位置。
資料庫索引做的事情很接近:它不是複製整張資料表給你,而是建立一套能快速定位資料的位置資訊。
例如對 email 建立索引:
CREATE INDEX idx_users_email ON users(email);
之後資料庫尋找某個 email 時,就可能先從 idx_users_email 找到對應位置,再取得真正的資料列,而不是掃過所有使用者。
注意這裡的「可能」。SQL 是描述你想得到什麼,不是命令資料庫一定要用哪個索引。最後採用什麼方式,通常由 Query Optimizer 決定。
為什麼索引能比較快?
如果一張表有 1,000 萬筆資料,逐筆檢查最壞情況可能需要檢查非常多筆。
而許多關聯式資料庫常見的索引實作會使用 B-tree 或其變體。B-tree 不是把所有值排成一條超長的清單,而是用階層式、保持排序的結構縮小搜尋範圍。
概念上可以想成:
這有點像你在字典找「network」時,不會從 A 的第一個單字一路往後讀,而會利用字母順序快速縮小範圍。
因此資料量越大,索引與逐筆掃描之間的差距通常越容易顯現。
Primary Key 也常常有索引
很多人第一次接觸 Index 時,以為只有自己寫 CREATE INDEX 才會出現索引。其實在常見資料庫中,Primary Key、Unique Constraint 等機制通常也會伴隨某種索引結構,但實際行為依資料庫引擎而異。
例如:
SELECT * FROM users WHERE id = 9527;
如果 id 是主鍵,資料庫通常不需要傻傻掃過前面 9,526 筆才找到它。
這也是為什麼「用 ID 查一筆資料」往往非常自然且快速。
那為什麼不把每個欄位全部加索引?
因為 Index 不是免費的。
索引本身要占儲存空間,而且資料改變時,相關索引也必須維護。
例如新增一位使用者:
INSERT INTO users (...) VALUES (...);
資料庫除了寫入 users 表,如果 name、email、created_at 都各自有索引,還可能需要同步更新這些索引。
因此索引通常是在做交換:
讀取更快,但需要額外空間,且 INSERT、UPDATE、DELETE 可能增加成本。
所以「索引很多」不等於「資料庫很快」。真正的問題是:你的查詢模式需要哪些索引?
複合索引:一個索引可以包含多個欄位
假設網站經常查詢:
SELECT * FROM orders
WHERE user_id = 42
AND created_at >= '2026-09-01';
你可以考慮建立複合索引:
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at);
這個索引同時包含 user_id 與 created_at。
但順序很重要。
(user_id, created_at) 和 (created_at, user_id) 並不是可以永遠互換的兩種寫法。索引的欄位順序會影響哪些查詢能有效利用它。
常見的理解方式是把複合索引想成電話簿:如果電話簿先依姓氏,再依名字排序,你可以很方便地找「姓 Chen 的人」,也可以在 Chen 裡再找某個名字;但如果只知道名字,不知道姓氏,原本的排序就不一定能提供同樣程度的幫助。
不同資料庫、查詢條件與最佳化器可能有不同結果,所以不要把簡化規則當成絕對定律。最可靠的方法仍然是查看實際 Query Plan。
Query Plan:不要猜資料庫在做什麼
你寫下 SQL 後,資料庫通常會評估不同執行方式,例如:
要不要使用某個索引?
要不要掃整張表?
兩張表要用什麼順序 Join?
預估會讀到多少資料?
這些決策形成 Query Plan,也就是查詢執行計畫。
許多資料庫可以使用 EXPLAIN 查看,例如:
EXPLAIN SELECT * FROM users
WHERE email = 'chen@example.com';
如果你正在處理慢查詢,「我明明有建索引」不是結論。應該確認資料庫實際有沒有使用它,以及它估計需要讀多少資料。
有時候資料庫甚至會故意不使用索引。
有索引,為什麼資料庫還可能選擇 Full Table Scan?
想像 users 表裡有一個 enabled 欄位,而 99% 的使用者都是 enabled = true。
執行:
SELECT * FROM users WHERE enabled = true;
即使 enabled 有索引,這個條件仍然會命中幾乎整張表。資料庫如果先讀索引,再回頭取得大量資料列,可能並不比直接掃表划算。
這牽涉到 Selectivity,也就是一個條件能把資料範圍縮小多少。
像 email、user_id 這種值通常較能精準定位資料;只有 true / false 的欄位,單獨作為索引時常常沒有那麼強的區分能力。
所以「WHERE 用到的欄位都加 Index」不是好的資料庫設計原則。
Index 也不是讓 SQL 自動變快的魔法
下面幾種情況都可能讓索引效果不如預期:
查詢需要取得表中非常高比例的資料;索引欄位順序與查詢模式不合;在欄位上做某些運算或轉換,使一般索引難以直接利用;資料庫統計資訊讓最佳化器判斷其他方案更便宜;索引存在,但查詢最後仍需要大量額外讀取。
因此效能調校不應該是:
比較合理的流程是:
這裡最重要的字是「測量」。
Index 和 ORDER BY 也有關
索引除了幫助 WHERE,有時也能幫助排序。
例如系統經常需要取得某個使用者最新的訂單:
SELECT * FROM orders
WHERE user_id = 42
ORDER BY created_at DESC
LIMIT 20;
如果索引設計與這種查詢模式相符,資料庫可能不必先找出大量訂單再另外做昂貴排序,而能更有效率地取得需要的資料。
這也是為什麼設計索引不能只看「有哪些欄位」,還要看完整查詢:WHERE、JOIN、ORDER BY、LIMIT 都可能影響最佳方案。
Index 與 Unique Constraint 不完全是同一件事
Index 的主要目的通常是協助資料存取效率;Unique Constraint 則是在表達資料規則:某個值不可以重複。
例如 email 必須唯一:
UNIQUE(email)
許多資料庫會透過唯一索引來實作這個限制,但概念上「我要確保資料不重複」和「我要讓查詢更快」仍是不同層次的需求。
設計 Schema 時,不應該因為某個實作剛好用了 Index,就把資料完整性與效能最佳化混為一談。
一個實際情境
假設 Norelyn 有一張 articles 表:
id
slug
category
published_at
title
content
網站最常做的事情可能有:
用 slug 開啟單篇文章;依 category 顯示文章;依 published_at 顯示最新文章。
那麼你真正應該研究的是這些查詢如何發生,而不是看到六個欄位就建立六個索引。
例如 slug 若具有唯一性,而且每次開文章都用它定位單篇內容,那它很可能是很值得最佳化的查詢路徑。另一方面,content 是大段文章正文,通常不會因為「它也是欄位」就建立普通 B-tree 索引;全文搜尋往往需要不同的索引與搜尋機制。
資料庫設計其實是在回答一個很實際的問題:系統會怎麼使用這些資料?
最後記住三件事
第一,Index 是額外的資料結構,不是資料表本身。它的目的,是讓某些資料存取不必每次從頭掃到尾。
第二,Index 有成本。它會占空間,也會增加資料寫入與維護的負擔,所以不是越多越好。
第三,真正可靠的效能最佳化來自 Query Plan 與測量,而不是看到慢查詢就盲目建立索引。
當一個網站只有幾百筆資料時,你可能很難感覺這些差異。但當資料量逐漸成長,Database Index 往往就是「SQL 看起來完全一樣,系統卻能不能撐住」的重要分界之一。
把概念放回真正的系統流程裡看,通常比只背名詞更容易理解它。