爲什麼推薦InnoDB引擎使用自增主鍵?

原文地址:http://www.ywnds.com/?p=8735

在使用InnoDB存儲引擎時,如果沒有特別的需要,請永遠使用一個與業務無關的自增字段作爲主鍵,除非高併發寫入操作可能需要衡量自增主鍵,後面會講。

經常看到有帖子或博客討論主鍵選擇問題,有人建議使用業務無關的自增主鍵,有人覺得沒有必要,完全可以使用如學號或身份證號這種唯一字段作爲主鍵。不論支持哪種論點,大多數論據都是業務層面的。如果從數據庫索引優化角度看,使用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中按主鍵順序插入可能會造成明顯的爭用。主鍵上界會成爲”熱點”,因爲所有的插入都發生在這裏,所以併發插入可能導致間隙鎖競爭。另一個熱點可能是AUTO_INCREMENT鎖機制:如果遇到這個問題,則可能需要考慮重新設計表或者應用,或者更改innodb_autoinc_lock_mode配置。

自增長在數據庫中是非常常見的一種屬性,也是很多DBA或開發人員首選的主鍵方式。在InnoDB存儲引擎的內存結構中,對每個含有自增長值的表都有一個自增長計數器。當對含有自增長的計數器的表進行插入操作時,這個計數器會被初始化,執行如下的語句來得到計數器的值:

插入操作會依據這個自增長的計數器值加1賦予自增長列。這個實現方式稱爲AUTO-INC Locking。這種鎖其實是採用一種特殊的表鎖機制,爲了提高插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增長值插入的SQL語句後立即釋放。

雖然AUTO-INC Locking從一定程度上提高了併發插入的效率,但還是存在一些性能上的問題。首先,對於有自增長值的列的併發插入性能較差,事務必須等待前一個插入的完成,雖然不用等待事務的完成。其次,對於INSERT….SELECT的大數據的插入會影響插入的性能,因爲另一個事務中的插入會被阻塞。

從MySQL 5.1.22版本開始,InnoDB存儲引擎中提供了一種輕量級互斥量的自增長實現機制,這種機制大大提高了自增長值插入的性能。並且從該版本開始,InnoDB存儲引擎提供了一個參數innodb_autoinc_lock_mode來控制自增長的模式,該參數的默認值爲1。在繼續討論新的自增長實現方式之前,需要對自增長的插入進行分類。如下說明:

  • insert-like:指所有的插入語句,如INSERT、REPLACE、INSERT…SELECT,REPLACE…SELECT、LOAD DATA等。
  • simple inserts:指能在插入前就確定插入行數的語句,這些語句包括INSERT、REPLACE等。需要注意的是:simple inserts不包含INSERT…ON DUPLICATE KEY UPDATE這類SQL語句。
  • bulk inserts:指在插入前不能確定得到插入行數的語句,如INSERT…SELECT,REPLACE…SELECT,LOAD DATA。
  • mixed-mode inserts:指插入中有一部分的值是自增長的,有一部分是確定的。入INSERT INTO t1(c1,c2) VALUES(1,’a’),(2,’a’),(3,’a’);也可以是指INSERT…ON DUPLICATE KEY UPDATE這類SQL語句。

接下來分析參數innodb_autoinc_lock_mode以及各個設置下對自增長的影響,其總共有三個有效值可供設定,即0、1、2,具體說明如下:

  • 0:這是MySQL 5.1.22版本之前自增長的實現方式,即通過表鎖的AUTO-INC Locking方式,因爲有了新的自增長實現方式,0這個選項不應該是新版用戶的首選了。
  • 1:這是該參數的默認值,對於”simple inserts”,該值會用互斥量(mutex)去對內存中的計數器進行累加的操作。對於”bulk inserts”,還是使用傳統表鎖的AUTO-INC Locking方式。在這種配置下,如果不考慮回滾操作,對於自增值列的增長還是連續的。並且在這種方式下,statement-based方式的replication還是能很好地工作。需要注意的是,如果已經使用AUTO-INC Locing方式去產生自增長的值,而這時需要再進行”simple inserts”的操作時,還是需要等待AUTO-INC Locking的釋放。
  • 2:在這個模式下,對於所有”INSERT-LIKE”自增長值的產生都是通過互斥量,而不是AUTO-INC Locking的方式。顯然,這是性能最高的方式。然而,這會帶來一定的問題,因爲併發插入的存在,在每次插入時,自增長的值可能不是連續的。此外,最重要的是,基於Statement-Base Replication會出現問題。因此,使用這個模式,任何時候都應該使用row-base replication。這樣才能保證最大的併發性能及replication主從數據的一致。

這裏需要特別注意,InnoDB跟MyISAM不同,MyISAM存儲引擎是表鎖設計,自增長不用考慮併發插入的問題。因此在master上用InnoDB存儲引擎,在slave上用MyISAM存儲引擎的replication架構下,用戶可以考慮這種情況。

另外,InnoDB存儲引擎,自增持列必須是索引,同時必須是索引的第一個列,如果不是第一個列,會拋出異常,而MyiSAM不會有這個問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章