一、總結
沒有主鍵怎么辦?
- 如果定義了主鍵,那么InnoDB會使用主鍵作為聚簇索引
- 如果沒有定義主鍵,那么會使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作為聚簇索引
- 如果既沒有主鍵也找不到合適的非空索引,那么InnoDB會自動生成一個不可見的名為row_id的列名為GEN_CLUST_INDEX的聚簇索引,該列是一個6字節的自增數值,隨着插入而自增--補充:該全局row_id在代碼實現上使用的是bigint unsigned類型,但實際上只給row_id留了6字節,這種設計就會存在一個問題:如果全局row_id一直漲,一直漲,直到2的48冪次-1時,這個時候再+1,row_id的低48位都為0,結果在插入新一行數據時,拿到的row_id就為0,存在主鍵沖突的可能性。
自動生成的主鍵有什么問題?
- 使用不了主鍵索引,查詢會進行全表掃描
- 影響數據插入性能,插入數據需要生成ROW_ID,而生成的ROW_ID是全局共享的(InnoDB 維護了一個全局的 dictsys.row_id,所有未定義主鍵的表都共享該row_id),並發會導致鎖競爭,影響性能
二、詳細文章
問題
MySQL數據表使用InnoDB作為存儲引擎的時候,數據結構就是使用B+樹,而數據本身存儲在主鍵索引上,也就是通常所說的聚簇索引,也就是每個表都需要有個聚簇索引樹,但是,在建表的時候卻發現可以不用指定主鍵,那么MySQL對於沒有指定主鍵的表示如何處理的呢?
InnoDB索引
對於InnoDB,可以簡單地把所有數據視為索引,每一個索引都對應一個B+數,而主鍵對應的索引就是聚簇索引,表的所有數據都存儲在聚簇索引上,而除了聚簇索引的普通索引存儲的只是主鍵的引用,所以,查詢的時候對於普通索引需要進行回表才能取到具體數據。
缺少主鍵MySQL如何處理
既然InnoDB對數據的存儲必須依賴於主鍵,那么對於沒有創建主鍵的表,該怎么辦?
InnoDB對聚簇索引處理如下: - 如果定義了主鍵,那么InnoDB會使用主鍵作為聚簇索引 - 如果沒有定義主鍵,那么會使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作為聚簇索引 - 如果既沒有主鍵也找不到合適的非空索引,InnoDB會自動幫你創建一個不可見的、長度為6字節的row_id,而且InnoDB 維護了一個全局的 dictsys.row_id,所以未定義主鍵的表都共享該row_id,每次插入一條數據,都把全局row_id當成主鍵id,然后全局row_id加1
很明顯,缺少主鍵的表,InnoDB會內置一列用於聚簇索引來組織數據。而沒有建立主鍵的話就沒法通過主鍵來進行索引,查詢的時候都是全表掃描,小數據量沒問題,大數據量就會出現性能問題。
但是,問題真的只是查詢影響嗎?不是的,對於生成的ROW_ID,其自增的實現來源於一個全局的序列,而所以有ROW_ID的表共享該序列,這也意味着插入的時候生成需要共享一個序列,那么高並發插入的時候為了保持唯一性就避免不了鎖的競爭,進而影響性能。
Returns a new row id. @return the new id */ UNIV_INLINE row_id_t dict_sys_get_new_row_id(void) { row_id_t id; mutex_enter(&(dict_sys->mutex)); id = dict_sys->row_id; if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) { dict_hdr_flush_row_id(); } dict_sys->row_id++; mutex_exit(&(dict_sys->mutex)); return(id); }
缺少主鍵或者非空索引存在問題
- 使用不了主鍵索引,查詢會進行全表掃描
- 影響數據插入性能,插入數據需要生成ROW_ID,而生成的ROW_ID是全局共享的,並發會導致鎖競爭,影響性能
為每個表設置主鍵
既然知道InnoDB對數據的存儲和處理都是基於聚簇索引的,那么,在建表時候要注意主鍵的重要性,為每個表都設置一個主鍵,如果沒有合適的字段來作為主鍵,可以設置一個業務無關的的代理主鍵,可以是自增ID,也可以是UUID(建議使用自增ID,性能較好)。
總結
在理解InnoDB的數據結構之后自然而然就會知道主鍵的重要性,在建表的時候也不會忘記設置主鍵,無論表設計有無合適的唯一字段,都需要設置一個主鍵,提高性能的同時也是一種好的習慣,對於后續的拓展以及表之間關聯都有一定的拓展性。
參考
https://zhuanlan.zhihu.com/p/98084061