[MySQL]為什麼主鍵最好是有序遞增的

Duancf發表於2024-09-10

為什麼主鍵索引最好是有序遞增的

我們在建表的時候,都會預設將主鍵索引設定為自增的,具體為什麼要這樣做呢?又什麼好處?
InnoDB 建立主鍵索引預設為聚簇索引,資料被存放在了 B+Tree 的葉子節點上。也就是說,同一個葉子節點內的各個資料是按主鍵順序存放的,因此,每當有一條新的資料插入時,資料庫會根據主鍵將其插入到對應的葉子節點中。
如果我們使用自增主鍵,那麼每次插入的新資料就會按順序新增到當前索引節點的位置,不需要移動已有的資料,當頁面寫滿,就會自動開闢一個新頁面。因為每次插入一條新記錄,都是追加操作,不需要重新移動資料,因此這種插入資料的方法效率非常高。
如果我們使用非自增主鍵,由於每次插入主鍵的索引值都是隨機的,因此每次插入新的資料時,就可能會插入到現有資料頁中間的某個位置,這將不得不移動其它資料來滿足新資料的插入,甚至需要從一個頁面複製資料到另外一個頁面,我們通常將這種情況稱為頁分裂。頁分裂還有可能會造成大量的記憶體碎片,導致索引結構不緊湊,從而影響查詢效率。

舉個例子,假設某個資料頁中的資料是1、3、5、9,且資料頁滿了,現在準備插入一個資料7,則需要把資料頁分割為兩個資料頁:

image

出現頁分裂時,需要將一個頁的記錄移動到另外一個頁,效能會受到影響,同時頁空間的利用率下降,造成儲存空間的浪費。
而如果記錄是順序插入的,例如插入資料11,則只需開闢新的資料頁,也就不會發生頁分裂:

image

因此,在使用 InnoDB 儲存引擎時,如果沒有特別的業務需求,建議使用自增欄位作為主鍵。
另外,主鍵欄位的長度不要太大,因為主鍵欄位長度越小,意味著二級索引的葉子節點越小(二級索引的葉子節點存放的資料是主鍵值),這樣二級索引佔用的空間也就越小。

a、所有關鍵字都出現在葉子結點的連結串列中(稠密索引),且連結串列中的關鍵字恰好是有序的;

b、不可能在非葉子結點命中;

c、非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存(關鍵字)資料的資料層。

1、如果我們定義了主鍵(PRIMARY KEY)

那麼InnoDB會選擇主鍵作為聚集索引、如果沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的唯一索引作為主鍵索引、如果也沒有這樣的唯一索引,則InnoDB會選擇內建6位元組長的ROWID作為隱含的聚集索引(ROWID隨著行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。

2、資料記錄本身被存於主索引(一顆B+Tree)的葉子節點上

這就要求同一個葉子節點內(大小為一個記憶體頁或磁碟頁)的各條資料記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB預設為15/16),則開闢一個新的頁(節點)

3、如果表使用自增主鍵

那麼每次插入新的記錄,記錄就會順序新增到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁

4、如果使用非自增主鍵(如果身份證號或學號等)

由於每次插入主鍵的值近似於隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置,此時MySQL不得不為了將新記錄插到合適位置而移動資料,甚至目標頁面可能已經被回寫到磁碟上而從快取中清掉,此時又要從磁碟上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,後續不得不透過OPTIMIZE TABLE來重建表並最佳化填充頁面。

總結:如果InnoDB表的資料寫入順序能和B+樹索引的葉子節點順序一致的話,這時候存取效率是最高的,也就是下面這幾種情況的存取效率最高:

a、使用自增列(INT/BIGINT型別)做主鍵,這時候寫入順序是自增的,和B+數葉子節點分裂順序一致;

b、該表不指定自增列做主鍵,同時也沒有可以被選為主鍵的唯一索引(上面的條件),這時候InnoDB會選擇內建的ROWID作為主鍵,寫入順序和ROWID增長順序一致;

c、如果一個InnoDB表又沒有顯示主鍵,又有可以被選擇為主鍵的唯一索引,但該唯一索引可能不是遞增關係時(例如字串、UUID、多欄位聯合唯一索引的情況),該表的存取效率就會比較差。

相關文章