每張表都一定存在主鍵嗎?

關於這個問題,各位小夥伴們不妨先自己想一想,再往下尋找答案。

首先公佈結論:對於 InnoDB 儲存引擎來說,每張表都一定有個主鍵(Primary Key)!

讓人非常遺憾的是,網路上至今仍然有非常多的文章是這樣的描述:“一張表中必須有聚集索引,但不一定需要主鍵”。前半句是正確的,後半句是大錯特錯!

對於 InnoDB 儲存引擎來說,表採用的儲存方式稱為索引組織表(index organizedtable),也即表都是根據主鍵的順序來進行組織存放的。如果主鍵都沒有,表怎麼存?

那下面這段沒定義主鍵的建表語句是正確的嗎?

CREATE TABLE test(
a INT NOT NULL,
b INT NULL,
c INT NOT NULL,
d INT NOT NULL,
UNIQUE KEY(b),
UNIQUE KEY(d),
UNIQUE KEY(c)
);

當然是沒有任何問題的。

因為 不顯示定義主鍵 != 沒有主鍵

如果在建立表時沒有顯式地定義主鍵,InnoDB 儲存引擎會按如下方式選擇或建立主鍵:

  • 首先判斷表中是否有非空的唯一索引(Unique NOT NULL),如果有,則該列即為主鍵
  • 如果不符合上述條件,InnoDB 儲存引擎自動建立一個 6 位元組大小的指標 _rowid 作為主鍵

如果表中有多個非空唯一索引時怎麼辦呢? InnoDB 儲存引擎將選擇建表時第一個定義的非空唯一索引為主鍵。需要注意的是!主鍵的選擇根據的是非空唯一索引定義的順序,而不是建表時列的順序。

比如上面那段程式碼,有 a、b、c、d 四個列,b、c、d 三列上都有唯一索引。不過 b 列不是非空的,所以不可能成為主鍵了。而 d 列首先被定義為非空的唯一索引,所以 InnoDB 儲存引擎將其視為主鍵。

B+ 樹索引總覽

InnoDB 儲存引擎支援以下幾種常見的索引:

  • B+ 樹索引
  • 全文索引
  • 雜湊索引

所謂雜湊索引也就是得益於雜湊演算法的快速查詢特性,不過雜湊索引的致命缺點就是無法範圍查詢。並且 InnoDB 中雜湊索引是自適應的,也就是說 InnoDB 儲存引擎會根據表的使用情況自動為表生成雜湊索引,不能人為干預是否在一張表中生成雜湊索引

全文索引本文先暫且不做贅述。

再來看 B+ 樹索引,B+ 樹索引的本質就是 B+ 樹在資料庫中的實現,它是目前關係型資料庫系統中查詢最為常用的索引。

關於 B+ 樹的資料結構我就不詳細說了,B 代表平衡(Balance),而不是二叉(Binary),B+ 樹是從最早的平衡二叉樹演化而來的,但是 B+ 樹不是一個二叉樹。

簡單介紹下:B+ 樹是為磁碟或其他直接存取輔助裝置設計的一種平衡查詢樹。在 B+ 樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點上,各葉子節點之間通過雙向連結串列進行連線。

也就是說,B+ 樹的葉子節點儲存真正的記錄,而非葉子節點的存在是為了更快速的找到對應記錄所在的葉子節點。如下圖是一個高度為 2 的 B+ 樹:

另外,需要注意的是,B+ 樹索引並不能找到一個給定鍵值的具體“行”!B+ 樹索引能找到的只是被查詢資料行所在的“頁”。然後資料庫通過把頁讀入到記憶體,再在記憶體中進行查詢,最後得到要查詢的資料

肯定有些小夥伴會懵逼了,“頁” 又是什麼東西?

這就得說到 InnoDB 儲存引擎的邏輯儲存結構。

InnoDB 儲存引擎中,所有資料都被邏輯地存放在一個空間中,稱之為 表空間(tablespace),也就是說我們常說的表,可以看作是 InnoDB 儲存引擎邏輯結構的最高層。表空間又由 段(segment)區(extent)頁(page) 組成(頁有時也稱為塊 block)。如下圖:

頁是 InnoDB 磁碟管理的最小單位,在 InnoDB 儲存引擎中,預設每個頁的大小為 16KB。而頁裡面存放的東西就是一行一行的記錄。

我們接下來要說的 聚集索引(clustered inex)和輔助索引(secondary index)其實都是一種 B+ 樹索引。也就是說不管是聚集索引還是輔助索引,其內部都是 B+樹,即高度平衡的,葉子節點存放著所有的資料。(需要注意的是,索引是儲存引擎負責實現的,因此不是所有的儲存引擎都支援聚簇索引)

聚集索引與輔助索引不同之處就是,葉子節點存放的是否是一整行的資訊。下文我們會詳細解釋。

主鍵和聚集索引的關係

先來看聚集索引,上面我們說過,InnoDB 儲存引擎表是索引組織表結構,即表中資料都是按照主鍵順序進行存放的。而聚集索引就是按照每張表的主鍵構造一棵 B+ 樹,同時葉子節點中存放的即為表中一行一行的資料,所以聚集索引的葉子節點也被稱為資料節點。

也就是說,聚集索引能夠在 B+ 樹索引的葉子節點上直接找到資料。並且由於定義了資料的邏輯順序,查詢優化器能夠快速發現到底是哪一段範圍的資料頁需要掃描。比如使用者需要查詢一張使用者表,查詢最後註冊的 10 位使用者,由於 B+ 樹索引的葉子節點是基於雙向連結串列的,所以使用者可以快速找到最後一個數據頁,並取出 10 條記錄。這也就是為什麼大部分情況下查詢優化器傾向於採用聚集索引了。

可以這麼說:在聚集索引中,索引即資料,資料即索引

另外,由於資料頁只能按照一棵 B+ 樹進行查詢排序,或者說無法同時把資料行存放在兩個不同的地方,所以每張表只能擁有一個聚集索引

講了這麼多,好像還沒講到主鍵和聚集索引有啥區別。一張表只能有一個主鍵,並且也只能有一個聚集索引,聚集索引還是按照主鍵來構建的,那這種種跡象不都表明主鍵就是聚集索引?

事實上,主鍵和索引就不是一個層次的東西!

主鍵是一種約束,這個約束用來強制表的實體完整性,一個表中只能有一個主鍵約束,並且主鍵約束中的列值必須是非空且唯一的。

而聚集索引它作為一種索引,其目的不是為了約束啥,而是為了對資料行進行排序以提高查詢的效率,換句話說它決定的是資料庫的物理儲存結構。

形象點說,一個沒加聚集索引的表,它的資料是一行一行 無序 地存放在磁碟儲存器上的。而如果給表添加了聚集索引,那麼表在磁碟上的儲存結構就由一行一行排列的結構轉變成了 樹狀結構,也就是 B+ 樹結構,換句話說,就是整個表就變成了一個索引,也就是上面提到的 “索引即資料,資料即索引”。

而至於 “主鍵就是索引” 這種觀點的由來,是因為:InnoDB 儲存引擎中,每張表都一定存在主鍵(顯示或隱式),而聚集索引依賴於主鍵的建立,所以如果沒有強制指定使用非聚集索引,InnoDB 在建立主鍵的同時會建立一個唯一的聚集索引(也有些文章稱之為 主鍵索引)。

所以,不要說 “主鍵就是聚集索引”,應該這樣說:“聚集索引一般都是加在主鍵上的”。

聚集索引和輔助索引的關係

輔助索引(Secondary Index)也稱為 非聚集索引、二級索引。其和聚集索引的最大區別就在於,輔助索引的葉子節點並不包含行記錄的全部資料。

簡單來說,一行記錄我們可以用 “主鍵 + 其他資料” 這樣的組合來標識,聚集索引中的葉子節點儲存的就是這一整個組合,而非聚集索引中的葉子節點只儲存了這個組合中的主鍵,那其他資料我怎麼獲得呢?

非聚集索引的葉子節點說還包含了一個 書籤(bookmark),該書籤用來告訴 InnoDB 儲存引擎哪裡可以找到與索引相對應的行資料。

那各位不妨想一想,行資料儲存在哪裡呢?

沒錯,上文說過,聚集索引中的葉子節點中存放的就是表中一行一行的資料,所以 InnoDB 儲存引擎的輔助索引中的書籤其實就是相應行資料的聚集索引鍵

也就是說,輔助索引的葉子節點包含的是:每行資料的主鍵 + 該行資料對應的聚集索引鍵

當通過輔助索引來尋找資料時,InnoDB 儲存引擎會先遍歷輔助索引並通過葉子節點獲得某個主鍵對應的聚集索引鍵,然後再通過聚集索引來找到一個完整的行記錄。

舉個例子,如果在一棵高度為 3 的輔助索引樹中查詢資料,那需要對這棵輔助索引樹遍歷 3 次找到指定聚集索引鍵,如果聚集索引樹的高度同樣為 3,那麼還需要對聚集索引樹進行 3 次查詢,最終找到一個完整的行資料所在的頁,因此一共需要 6 次邏輯 IO 訪問以得到最終的一個數據頁。

另外,很顯然的是,輔助索引的存在並不影響資料在聚集索引中的組織,因此每張表上可以有多個輔助索引

關注公眾號 | 飛天小牛肉,即時獲取更新

  • 博主東南大學碩士在讀,攜程 Java 後臺開發暑期實習生,利用課餘時間運營一個公眾號『 飛天小牛肉 』,2020/12/29 日開通,專注分享計算機基礎(資料結構 + 演算法 + 計算機網路 + 資料庫 + 作業系統 + Linux)、Java 技術棧等相關原創技術好文。本公眾號的目的就是讓大家可以快速掌握重點知識,有的放矢。關注公眾號第一時間獲取文章更新,成長的路上我們一起進步

  • 並推薦個人維護的開源教程類專案: CS-Wiki(Gitee 推薦專案,現已累計 1.8k+ star), 致力打造完善的後端知識體系,在技術的路上少走彎路,歡迎各位小夥伴前來交流學習 ~

  • 如果各位小夥伴春招秋招沒有拿得出手的專案的話,可以參考我寫的一個專案「開源社群系統 Echo」Gitee 官方推薦專案,目前已累計 900+ star,基於 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 並提供詳細的開發文件和配套教程。公眾號後臺回覆 Echo 可以獲取配套教程,目前尚在更新中。