在Mysql表設計中,一般會使用一個與業務無關的自增列作爲主鍵。
這是由於Mysql默認使用B-Tree索引,你能夠簡單理解爲「排好序的快速查找結構」。
以下是一個B-Tree的結構圖,2層B+樹,每一個頁面的扇出爲4;並有1到6五條記錄;上層記錄保存每一個頁面的最小值;每一個頁面經過雙向鏈表連接起來的;
當你插入記錄7時,就會發生頁面分裂:

如上可見分裂產生了記錄移動,可是優化後的分裂操做無需記錄移動:

在InnoDB的實現中,爲每一個索引頁面維護了一個上次插入的位置,以及上次的插入是遞增/遞減的標識。根據這些信息,InnoDB可以判斷出新插入到頁面中的記錄,是否仍舊知足遞增/遞減的約束,若知足約束,則採用優化後的分裂策略;
因此建議使用一列順序遞增的 ID 來做爲主鍵,但沒必要是數據庫的autoincrement字段,只要知足順序增長便可 。不少大型應用會有順序遞增的ID生成器。
測試以下:mysql
- CREATE TABLE `table1` (
- `id` int(10) NOT NULL AUTO_INCREMENT,
- `text` varchar(255) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=200001 DEFAULT CHARSET=utf8
-
- CREATE TABLE `table2` (
- `id` int(10) NOT NULL,
- `text` varchar(255) NOT NULL,
- KEY `id` (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8
腳本以下:sql
- $link = mysql_connect('127.0.0.1', 'root', 'mckee');
- mysql_select_db('test', $link);
-
- $count = 200000;
- $table1_data = range(1, $count);
- $text = 'just test!just test!just test!just test!just test!';
-
- $time1 = get_time();
- foreach ($table1_data as $row) {
- $id = rand(1,100000000);
- mysql_query("insert into table1(text) values ('{$text}')");
- }
- $time2 = get_time();
- foreach ($table1_data as $row) {
- $id = rand(1,100000000);
- mysql_query("insert into table2(id, text) values ({$id}, '{$text}')");
- }
- $time3 = get_time();
-
- echo 'tabe1 insert execute time:' . ($time2 - $time1) . PHP_EOL;
- echo 'tabe2 insert execute time:' . ($time3 - $time2) . PHP_EOL;
-
- function get_time()
- {
- list( $usec , $sec ) = explode ( " " , microtime ());
- return ((float) $usec + (float) $sec );
- }