> 經常看到有帖子或博客討論主鍵選擇問題,有人建議使用業務無關的自增主鍵,有人覺得沒有必要,完全可以使用如學號或身份證號這種唯一字段作為主鍵。不論支持哪種論點,大多數論據都是業務層面的。如果從數據庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意。下面從各個方面來討論一下。
在使用InnoDB存儲引擎時,如果沒有特別的需要,請永遠使用一個與業務無關的自增字段作為主鍵,除非高并發寫入操作可能需要衡量自增主鍵或有業務安全性要求,后面會講。
一、首先不管主鍵策略是什么,這兩點都是必須遵守的。
1. 主鍵不可修改
對于數據庫來說,主鍵其實是可以修改的,只要不和其他主鍵沖突就可以。但是,對于應用來說,如果一條記錄要修改主鍵,那就會出大問題。
因為主鍵的第二個作用是讓其他表的外鍵引用自己,從而實現關系結構。一旦某個表的主鍵發生了變化,就會導致所有引用了該表的數據必須全部修改外鍵。很多Web應用的數據庫并不是強約束(僅僅引用主鍵但并沒有設置外鍵約束),修改主鍵會導致數據完整性直接被破壞。
2. 業務字段不可用于主鍵
所有涉及到業務的字段,無論它看上去是否唯一,都決不能用作主鍵。例如,用戶表的Email字段是唯一的,但是,如果用它作主鍵,就會導致其他表到處引用Email字段,從而泄露用戶信息。
類似的,看上去唯一的用戶名、身份證號等,也不能用作主鍵。對這些唯一字段,應該加上unique索引約束。
此外,修改Email實際上是一個業務操作,這個操作就直接違反了上一條原則。
那么,主鍵應該使用哪個字段呢?
主鍵必須使用單獨的,完全沒有業務含義的字段,也就是主鍵本身除了唯一標識和不可修改這兩個責任外,主鍵沒有任何業務含義。
二、主鍵應該用什么類型?
上面說了,不考慮業務,從數據庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意。
下面先簡單說說MySQL索引實現。在MySQL中,索引屬于存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。
**MyISAM存儲引擎**
MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址。
下圖是MyISAM索引的原理圖:

這里設表一共有三列,假設我們以Col1為主鍵,則上圖是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。
如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:

同樣也是一顆B+Tree,data域保存數據記錄的地址。
> 因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。
MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分。
**InnoDB存儲引擎**
雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。
第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。
> 因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。
第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。
例如,下圖為定義在Col3上的一個輔助索引:

這里以英文字符的ASCII碼作為比較準則。
> 聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
了解不同存儲引擎的索引實現方式對于正確使用和優化索引都非常有幫助,
> 例如,知道了InnoDB的索引實現后,就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。
**InnoDB自增主鍵**
上文討論過InnoDB的索引實現,InnoDB使用聚集索引,數據記錄本身被存于主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小為一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB默認為15/16),則開辟一個新的頁(節點)。
如果表使用自增主鍵,那么每次插入新的記錄,記錄就會順序添加到當前索引節點的后續位置,當一頁寫滿,就會自動開辟一個新的頁。如下圖所示:

這樣就會形成一個緊湊的索引結構,近似順序填滿。由于每次插入時也不需要移動已有數據,因此效率很高,也不會增加很多開銷在維護索引上。
如果使用非自增主鍵(如果身份證號或學號等),由于每次插入主鍵的值近似于隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置:

此時MySQL不得不為了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,后續不得不通過OPTIMIZE TABLE來重建表并優化填充頁面。
因此,只要可以,請盡量在InnoDB上采用自增字段做主鍵。
另外,InnoDB存儲引擎,自增持列必須是索引,同時必須是索引的第一個列,如果不是第一個列,會拋出異常,而MyiSAM不會有這個問題。
* * * * *
總結
1. InnoDB使用聚集索引,數據記錄本身被存于主索引的葉子節點上,這就要求同一個葉子節點內的各條數據記錄按主鍵順序存放,因此每當一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子,則開辟一個新的頁(節點)如果表使用自增主鍵,那么每次插入新的記錄時,記錄就會順序添加到當前索引節點后續位置,當一頁寫滿,就會自動開辟一個新的頁。這樣就就會形成一個緊湊的索引結構,近似順序填滿,由于每次插入時也不需要移動所有數據,因此效率很高,也不會增加很多額外的開銷維護索引。
2. 如果使用非自增主鍵,由于每次插入主鍵的值近乎于隨機,因此每次新紀錄都要被插到現有索引頁的中間某個位置,此時MySQL不得不為了將新紀錄插到合適位置而移動數據,甚至目標頁面可能已經被寫到磁盤而從緩存中清除,這增加了很多額外開銷,同時頻繁的移動,分頁造成了大量的碎片,得到不夠緊湊的索引結構,后續不得不通過OPTIMIZE TABLE來重建并優化填充頁面。
2. 由于MySQL從磁盤讀取數據時一塊一塊來讀取的,同時,根據局部性原理,MySQL引擎會選擇預讀一部分和你當前讀數據所在內存相鄰的數據塊,這個時候這些相鄰數據塊的數據已經存在于內存中。由于數據庫大部分是查詢操作,這個時候,如果主鍵是自增的話,數據存儲都是緊湊地存儲在一起的,那么對于局部性原理利用和避免過多地I/O操作都有著巨大的促進作用
- 授權管理
- 角色管理
- 設置密碼
- 5.6 版本
- 系統用戶
- 當前用戶
- 目錄
- 設計規劃
- 數據字典
- 狀態監控
- 查看MYSQL表占用空間狀態
- show table status
- SHOW 命令
- SHOW TABLE STATUS
- 表格輸出
- 調優
- 書籍培訓
- 數據庫管理員的第一本書(原書第2版)
- 視頻
- 收獲,不止SQL優化
- 基本概念
- 工具
- phpMyadmin
- 變更管理
- 數據關系與原則
- 數據完整性
- 業務完整性
- 字段更新(1)
- 訂單應用(1)
- 訂單應用(2)
- 表間數據連接
- 數據管理
- Cheet Sheet
- Database Administrator
- 索引設計
- Mysql 四種常見的索引
- MySQL索引之主鍵索引
- MySQL索引使用對查詢、插入速度的影響
- 查詢優化
- 存儲優化
- 分割數據表字段
- Procedure_Analyse優化表結構
- 性能優化
- 拆分DELETE/INSERT語句
- MySQL命令
- 表復制
- 如何快速創建相同結構的表
- 主鍵設計
- 為什么推薦InnoDB引擎使用自增主鍵?
- INFORMATION_SCHEMA
- _5.6版本
- USER_PRIVILEGES