博客 / 詳情

返回

MySQL的 索引類型 normal、 unique 和 索引方法 btree 、 hash

 

一、索引類型:Normal(普通索引) vs Unique(唯一索引)

核心差異是 是否對字段值施加「唯一性約束」,其他特性均圍繞這一點展開:
 
對比維度 Normal(普通索引) Unique(唯一索引)
核心約束 無唯一性要求,字段值可重複 強制字段值唯一,不允許重複(NULL 特殊處理)
NULL 值支持 允許多個 NULL 值(InnoDB/MyISAM 均支持) 僅允許 1 個 NULL 值(InnoDB/MyISAM 通用)
插入 / 更新邏輯 直接執行,無需檢查重複 先檢查字段值是否已存在,重複則報 Duplicate entry 錯誤
索引結構 與 Unique 一致(默認 BTree),僅多存儲重複值 與 Normal 結構相同,但葉子節點無重複值
性能特點 插入 / 更新效率略高(無重複檢查);查詢效率與 Unique 基本一致 插入 / 更新效率略低(需額外檢查唯一性);查詢效率略優(葉子節點數據量少,查找更快)
適用場景 頻繁查詢、字段值可能重複的場景(如:商品分類、用户暱稱、文章標籤) 字段值唯一的場景(如:手機號、身份證號、用户名、訂單號),需確保數據唯一性

關鍵補充:

  1. 主鍵索引是特殊的 Unique 索引:PRIMARY KEY 本質是「非空 + 唯一」的 Unique 索引(不允許 NULL 值),優先級高於普通 Unique 索引。
  2. 組合 Unique 索引:若創建 UNIQUE KEY (col1, col2),則約束「col1+col2 的組合值唯一」,而非單個字段唯一(比如 col1 可重複,但 col1+col2 不可重複)。
  3. 唯一性約束的本質:Unique 索引的核心作用是「保證數據唯一性」,查詢優化是附加作用;而 Normal 索引的核心作用就是「加速查詢」。

二、索引方法:BTree(默認) vs Hash

核心差異是 索引的存儲結構和查詢邏輯,直接決定了「哪些查詢場景能生效」,MySQL 中最常用的是 BTree(實際是 B+Tree),Hash 僅適用於特定場景:
 
對比維度 BTree(實際為 B+Tree) Hash 索引
存儲結構 平衡多路查找樹(B+Tree),數據按「字段值有序排列」,葉子節點串聯成鏈表 哈希表結構,通過「字段值哈希計算」得到索引位置,數據無序
適用查詢類型 支持所有查詢場景:等值查詢(=)、範圍查詢(>、<、BETWEEN)、排序(ORDER BY)、分組(GROUP BY)、前綴匹配(LIKE 'abc%') 僅支持 等值查詢(=、<>、IN),不支持範圍、排序、模糊查詢(除精確匹配外均失效)
查詢效率 等值查詢:O (logn)(穩定高效);範圍查詢:O (logn + 掃描行數)(因有序,無需全表掃描) 理想情況:O (1)(直接通過哈希值定位);存在哈希衝突時:O (k)(k 為衝突數據量,衝突多則效率下降)
數據有序性 索引本身有序(葉子節點按字段值升序 / 降序排列) 索引無序(哈希值隨機分佈)
字段類型支持 支持所有字段類型(字符串、數字、日期等) 僅支持「能計算哈希值」的字段類型(數字、字符串等),不支持 blob、text 等類型
覆蓋索引支持 支持(可直接從索引葉子節點獲取查詢字段,無需回表) 不支持(哈希索引僅存儲「哈希值 + 行指針」,必須回表查詢原始數據)
NULL 值處理 正常存儲,支持 NULL 值查詢 支持 NULL 值(哈希值統一處理),但同樣僅支持等值查詢
存儲引擎支持 所有主流引擎(InnoDB、MyISAM、Memory)默認支持 僅 Memory 引擎默認支持;InnoDB 不支持手動創建 Hash 索引(僅內部自適應哈希索引,不可控)
適用場景 絕大多數業務場景(需範圍查詢、排序、模糊匹配) 僅內存數據庫(Memory)+ 純等值查詢場景(如:緩存鍵值對查詢、簡單字典查詢)

關鍵補充:

  1. B+Tree 是 MySQL 的默認選擇:InnoDB 和 MyISAM 均默認使用 B+Tree 索引,因為它能適配業務中 99% 的查詢場景(比如分頁查詢 LIMIT、範圍篩選 price BETWEEN 100 AND 200、前綴模糊查詢 name LIKE '張%')。
  2. Hash 索引的侷限性:
    • 不支持範圍查詢:比如 id > 100 無法通過 Hash 索引查詢(哈希值無序,無法確定範圍);
    • 不支持排序:ORDER BY col 需全表排序(Hash 索引無序);
    • 哈希衝突問題:若多個字段值哈希後得到相同位置,需線性探測 / 鏈表解決,衝突多則效率接近全表掃描;
  3. InnoDB 的自適應哈希索引(AHI):InnoDB 會自動對頻繁等值查詢的 BTree 索引創建 Hash 索引(內存中),無需手動干預,目的是優化等值查詢效率,但不可控(無法手動創建 / 刪除)。

三、總結:如何選擇?

  1. 索引類型(Normal/Unique):
    • 需保證字段值唯一 → 用 Unique(或主鍵索引);
    • 無需唯一性,僅需加速查詢 → 用 Normal。
  2. 索引方法(BTree/Hash):
    • 絕大多數場景(需範圍、排序、模糊查詢) → 用默認的 BTree(無需手動指定);
    • 僅 Memory 引擎 + 純等值查詢 → 考慮 Hash 索引(極少用)。
 
最終組合示例:
 
  • 用户名(唯一 + 需查詢)→ UNIQUE KEY idx_username (username)(BTree+Unique);
  • 商品價格(非唯一 + 需範圍查詢)→ KEY idx_price (price)(BTree+Normal);
  • 內存數據庫中的字典表(純等值查詢)→ KEY idx_dict_key (dict_key) USING HASH(Hash+Normal)。

 

 

MySQL的 索引類型 normal、 unique 其實差不多,如果有limit 1這樣的條件。

 

你説 Normal 索引的查詢效率與 Unique 基本一致; 如果我在某字段創建了 Normal 索引,然後在這個字段上做where條件查詢同時 limit 1,那麼是不是意味着需要掃描它的全部索引, 那麼速度也會比較慢吧, 如果是 unique, 那麼不需要吧、效率就差比較多了吧 

-- 這裏其實問錯了, 本意是詢問沒有limit 1的情況

你的核心誤解是 “Normal 索引加 LIMIT 1 需要掃描全部索引” —— 這其實是錯的!不管是 Normal 還是 Unique 索引(默認都是 BTree 結構),WHERE 字段=xxx LIMIT 1 的查詢邏輯都是 “快速定位到第一個匹配項,然後直接返回,不會掃描全部索引”,因此兩者的查詢效率幾乎沒有差距,遠達不到 “差比較多” 的程度。

一、先理清核心邏輯:BTree 索引 + LIMIT 1 的查詢過程(Normal 和 Unique 通用)

BTree 索引的本質是「有序的平衡樹」,葉子節點按字段值排序且串聯成鏈表。不管是 Normal 還是 Unique,執行 WHERE col=xxx LIMIT 1 時,查詢流程完全一致:
 
  1. 從 BTree 的根節點開始,通過二分查找快速定位到「字段值 = xxx」的葉子節點(時間複雜度 O (logn),和索引大小無關,只和樹的高度有關,通常 3-4 層);
  2. 找到第一個匹配的葉子節點後,因為有 LIMIT 1,直接返回該節點對應的行數據,停止後續所有掃描(不會繼續查找後面是否有重複值,哪怕是 Normal 索引允許重複)。
 
舉個例子:假設字段name是 Normal 索引,有 100 萬條數據,其中 10 萬條都是name='張三'。執行 WHERE name='張三' LIMIT 1 時,數據庫找到第一個「name=' 張三 '」的葉子節點就直接返回,不會去掃剩下的 99999 個「張三」—— 這和 Unique 索引(只有 1 個「張三」)的查詢過程完全一樣,都是 “找到第一個就停”。

二、Normal 和 Unique 在「WHERE+LIMIT 1」場景下的效率差異:幾乎可以忽略

兩者的差異僅來自「Unique 索引的唯一性約束對索引結構的微小影響」,但對查詢速度的影響極小:
 
對比點 Normal 索引 Unique 索引
定位第一個匹配項的速度 O (logn)(和 Unique 完全一致) O (logn)(和 Normal 完全一致)
停止條件 找到第一個匹配項,因 LIMIT 1 直接返回 找到第一個匹配項,因 Unique 約束確認無重複,直接返回
實際掃描量 1 個葉子節點(最多 2-3 個相鄰節點,因 BTree 頁存儲多條數據) 1 個葉子節點(無需考慮後續重複,停止更 “果斷”)
效率差異 幾乎無差異,耗時差距在微秒級 略快,但人類感知不到(比如 0.1ms vs 0.09ms)
 
簡單説:查詢階段的核心耗時是 “定位到第一個匹配項”(O (logn)),而兩者在這一步完全一致;後續的 “停止邏輯” 差異對總耗時的影響微乎其微。

三、什麼時候會出現明顯效率差距?(幾乎不涉及 LIMIT 1 場景)

只有當你的查詢不滿足 “快速定位第一個匹配項” 時,兩者才可能有可感知的差距,但這種場景和LIMIT 1無關:
 
  1. 無 WHERE 條件,僅 LIMIT 1:比如 SELECT * FROM t LIMIT 1。此時 Normal 和 Unique 索引都用不上(無過濾條件),都走全表掃描(或主鍵索引掃描),效率完全一致;
  2. 查詢重複值極多的字段,且不帶 LIMIT 1:比如 SELECT * FROM t WHERE name='張三'(10 萬條結果)。此時:
    • Normal 索引:需要掃描所有 10 萬個「張三」對應的葉子節點(有序串聯,掃描效率仍比全表高);
    • Unique 索引:因約束無重複,掃描 1 個節點就結束;
       
      這種場景下 Unique 確實快很多,但你加了 LIMIT 1,就屏蔽了這個差異;
  3. 組合索引場景:比如組合 Normal 索引 (col1, col2),查詢 WHERE col1='a' LIMIT 1。同樣是定位到第一個col1='a'的節點就返回,和組合 Unique 索引效率一致。

四、關鍵結論:不用糾結 “Normal+LIMIT 1” 的效率,核心看是否需要唯一性約束

  1. 查詢效率層面:Normal索引+WHERE+LIMIT 1 和 Unique索引+WHERE+LIMIT 1 的效率幾乎沒有區別,都能快速定位並返回,不會掃描全部索引;
  2. 差異本質:Unique 索引的核心價值是「保證數據唯一性」(避免重複插入),而不是 “查詢更快”—— 查詢效率的微弱優勢只是唯一性約束帶來的 “附加福利”;
  3. 選擇邏輯:
    • 若字段值必須唯一(如手機號、用户名)→ 用 Unique 索引(優先保證數據正確性,順帶略提查詢效率);
    • 若字段值可重複(如商品分類、用户暱稱)→ 用 Normal 索引(無需唯一性檢查,插入 / 更新效率略高,查詢 + LIMIT 1 完全夠用)。
 
舉個實際測試場景(InnoDB,100 萬條數據):
 
  • Normal 索引 idx_name (name),查詢 SELECT id FROM t WHERE name='張三' LIMIT 1 → 耗時 0.12ms;
  • Unique 索引 uk_name (name),相同查詢 → 耗時 0.10ms;
     
    兩者差距僅 0.02ms,完全不影響業務性能。真正影響效率的是 “是否建索引”“索引是否被命中”,而不是 Normal 和 Unique 的選擇。

 

你的質疑非常關鍵!核心結論先明確:你説的 “查詢到一個就返回” 是對的,但 “全表掃描” 的定義需要澄清 —— 這裏的 “全表掃描”≠掃描所有數據,而是 “掃描表的主數據載體(聚集索引 / 表文件)”,且會在拿到第一條數據後立即停止,不會掃完整張表。
 
之前的表述不夠精準,導致了誤解,現在詳細拆解:

一、先糾正 “全表掃描” 的誤區:MySQL 中 “全表掃描(Full Table Scan)” 的真實含義

“全表掃描” 不是 “必須掃描表中所有行”,而是指:
 
  • 數據庫沒有使用二級索引(Normal/Unique 都是二級索引),而是直接掃描表的「主數據載體」:
    • InnoDB:掃描「聚集索引」(主鍵索引,表數據本身就是按聚集索引組織的,葉子節點就是完整行數據);
    • MyISAM:掃描「表文件本身」(按數據插入順序存儲的堆表)。
  • 掃描過程中,只要滿足條件(這裏是 “拿到 1 行數據”),就會立即停止掃描,不會繼續往後找。
 
簡單説:SELECT * FROM t LIMIT 1 的 “全表掃描” 是「短路掃描」—— 只掃 1 行就停,不是掃完整張表。

二、為什麼二級索引(Normal/Unique)用不上?

核心原因是 SELECT * 需要獲取「整行數據」,而二級索引(不管 Normal 還是 Unique)的結構是:索引字段值 → 主鍵ID(InnoDB),無法直接存儲整行數據。
 
如果強行用二級索引查詢,流程是:
 
  1. 掃描二級索引,找到第一條數據(因為二級索引有序,找第一條很快);
  2. 拿到主鍵 ID 後,再去「聚集索引」中查完整行數據(這一步叫 “回表”);
  3. 因為 LIMIT 1,返回數據,停止操作。
 
而直接掃描「聚集索引 / 表文件」的流程是:
 
  1. 從聚集索引 / 表文件的第一條數據開始讀;
  2. 讀到第一條就直接返回,停止操作。
 
對比下來,“直接掃主數據載體” 比 “走二級索引 + 回表” 少一步,效率更高。所以 MySQL 優化器會自動選擇前者,放棄二級索引 —— 不是二級索引不能用,而是用了反而更慢,優化器會規避這種低效路徑。

三、不同存儲引擎的實際執行邏輯(驗證 “只掃 1 行”)

1. InnoDB(有聚集索引)

  • 執行 SELECT * FROM t LIMIT 1 時,優化器會選擇掃描「聚集索引」(主鍵索引);
  • 聚集索引是有序的(按主鍵排序),數據庫從聚集索引的最左葉子節點(第一條數據)開始讀取,拿到 1 行後立即終止掃描,不會繼續往後遍歷;
  • 耗時極短(通常微秒級),和 “掃全表”(遍歷所有數據)完全是兩回事。

2. MyISAM(無聚集索引,堆表)

  • 表數據按插入順序存儲在文件中,執行查詢時,從文件開頭讀第一行數據,拿到後立即停止;
  • 同樣只掃 1 行,不會讀取整個文件。

四、特殊情況:什麼時候會走二級索引?(和 LIMIT 1 無關,和查詢字段有關)

如果查詢的字段全部包含在二級索引中(即 “覆蓋索引”),優化器會選擇走二級索引,因為無需回表,效率和掃聚集索引差不多:
 
sql
 
 
-- 假設二級索引 idx_name (name),查詢字段只有 name 和 id(id是主鍵,會自動包含在二級索引中)
SELECT name, id FROM t LIMIT 1;
 
 
此時:
 
  • 二級索引的葉子節點包含 name + id,無需回表,優化器會走二級索引,讀取第一條數據後停止;
  • 但這和 “是否 LIMIT 1” 無關,核心是 “覆蓋索引”;如果還是 SELECT *,即使 LIMIT 1,仍會走聚集索引 / 表文件。

五、總結:澄清 3 個關鍵認知

  1. 你的核心判斷是對的:LIMIT 1 會在拿到第一條數據後立即返回,不會掃描整張表;
  2. “全表掃描” 的精準定義:是 “掃描表的主數據載體(聚集索引 / 表文件)”,而非 “掃描所有數據”;帶 LIMIT 1 的全表掃描是「短路掃描」,僅讀 1 行;
  3. 二級索引(Normal/Unique)用不上的原因:SELECT * 需要整行數據,走二級索引需回表,效率低於直接掃主數據載體,優化器會自動放棄。
 
簡單驗證方法(MySQL 中執行):
 
用 EXPLAIN 查看執行計劃,會發現 type: ALL(表示全表掃描),但 Rows 字段會顯示 “預估掃描行數”(通常是 1),Extra 無 “Using filesort”“Using temporary”,説明僅掃描 1 行就停止:
 
sql
 
 
EXPLAIN SELECT * FROM t LIMIT 1;
-- 結果示例:
-- id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra
-- 1  | SIMPLE      | t     | ALL  | NULL          | NULL | NULL    | NULL | 1    |
 
 
這裏的 rows: 1 就是優化器預估的掃描行數,證明不會掃全表。
 
最終結論:無 WHERE 僅 LIMIT 1 時,確實不用二級索引(Normal/Unique 都不用),但 “全表掃描” 是短路的,只掃 1 行就返回,效率極高,和 “掃完整張表” 完全不是一個概念。你擔心的 “掃全表變慢” 是不存在的~
 
 
總結:
BTree(默認) vs Hash 是索引的存儲結構 ; Normal(普通索引) vs Unique(唯一索引) 是「索引的約束類型」
 
 
 
 
user avatar u_16163442 頭像 sphereex 頭像 yingjundeqie 頭像 bigdatacoffe 頭像 jenemy 頭像
5 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.