為什麼主鍵索引最好是有序遞增的
我們在建表的時候,都會預設將主鍵索引設定為自增的,具體為什麼要這樣做呢?又什麼好處?
InnoDB 建立主鍵索引預設為聚簇索引,資料被存放在了 B+Tree 的葉子節點上。也就是說,同一個葉子節點內的各個資料是按主鍵順序存放的,因此,每當有一條新的資料插入時,資料庫會根據主鍵將其插入到對應的葉子節點中。
如果我們使用自增主鍵,那麼每次插入的新資料就會按順序新增到當前索引節點的位置,不需要移動已有的資料,當頁面寫滿,就會自動開闢一個新頁面。因為每次插入一條新記錄,都是追加操作,不需要重新移動資料,因此這種插入資料的方法效率非常高。
如果我們使用非自增主鍵,由於每次插入主鍵的索引值都是隨機的,因此每次插入新的資料時,就可能會插入到現有資料頁中間的某個位置,這將不得不移動其它資料來滿足新資料的插入,甚至需要從一個頁面複製資料到另外一個頁面,我們通常將這種情況稱為頁分裂。頁分裂還有可能會造成大量的記憶體碎片,導致索引結構不緊湊,從而影響查詢效率。
舉個例子,假設某個資料頁中的資料是1、3、5、9,且資料頁滿了,現在準備插入一個資料7,則需要把資料頁分割為兩個資料頁:
出現頁分裂時,需要將一個頁的記錄移動到另外一個頁,效能會受到影響,同時頁空間的利用率下降,造成儲存空間的浪費。
而如果記錄是順序插入的,例如插入資料11,則只需開闢新的資料頁,也就不會發生頁分裂:
因此,在使用 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、多欄位聯合唯一索引的情況),該表的存取效率就會比較差。