MySQL InnoDB 中,SELECT * FROM table 若無 ORDER BY,回傳順序不保證遵循主鍵,而是取決於優化器選擇的存取路徑。本文透過實測案例,揭示二級索引導致的「按索引序輸出」現象,並說明 FORCE INDEX、覆蓋索引、叢集索引掃描的機制與控制手段。
CREATE TABLE test_b (
Id INT NOT NULL,
Type INT DEFAULT NULL,
PRIMARY KEY (Id),
KEY IDX_Type (Type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO test_b VALUES (1,1), (2,6), (3,2), (7,3), (4,1);
SELECT * FROM test_b;
輸出:
| Id | Type |
|---|---|
| 1 | 1 |
| 4 | 1 |
| 3 | 2 |
| 7 | 3 |
| 2 | 6 |
順序為 Type 升序,且同 Type 下按 Id 升序 (1→4, 3, 7, 2→6)。
SELECT * FROM test_b FORCE INDEX (PRIMARY);
輸出: 按 Id 1,2,3,4,7 — 符合主鍵叢集索引順序。
SELECT * 需要所有欄位IDX_Type 葉節點包含 (Type, Id) → 覆蓋索引 (Covering Index),無需回表Id, Type 成本低於 掃描主鍵Index Full Scan on IDX_Type,輸出順序遵循索引鍵順序InnoDB 二級索引鍵值實為 (Type, Id),主鍵值隱式附加於二級索引鍵尾部 以保證唯一性。因此索引樹中 Type=1 下依 Id 排序,掃描自然產生 (1,1) → (4,1) 順序。
INSERT INTO test_b VALUES (9,3), (6,3), (10,3);
SELECT * FROM test_b;
輸出 Type=3 區段:6, 7, 9, 10 — 完全符合 (Type, Id) 排序。
| 需求 | 解法 | 原理 |
|---|---|---|
| 必須按主鍵序 | ORDER BY Id 或 FORCE INDEX (PRIMARY) | 明確語意 / 強制存取路徑 |
| 按特定索引序 | FORCE INDEX (idx_name) | 提示優化器 |
| 任意順序可接受 | 無 ORDER BY,接受優化器決定 | 效能最優 |
SELECT * 若剛好被二級索引覆蓋,優化器會毫不猶豫選擇之,導致輸出順序隨索引定義改變。ORDER BY 表達業務意圖。SQL Server 叢集索引同樣決定物理順序,但 SELECT * 無 ORDER BY 時,優化器通常選擇叢集索引掃描 (除非有覆蓋非叢集索引且極窄)。行為相似,但 SQL Server 更傾向維持叢集索引順序。
結論:永遠加上 ORDER BY。資料庫優化器的目標是「最低成本」,而非「符合你的直覺順序」。