背景:html
自增加是一個很常見的數據屬性,在MySQL中你們都很願意讓自增加屬性的字段當一個主鍵。特別是InnoDB,由於InnoDB的彙集索引的特性,使用自增加屬性的字段當主鍵性能更好,這裏要說明下自增主鍵須要注意的幾個事項。mysql
問題一:表鎖sql
在MySQL5.1.22以前,InnoDB自增值是經過其自己的自增加計數器來獲取值,該實現方式是經過表鎖機制來完成的(AUTO-INC LOCKING)。鎖不是在每次事務完成後釋放,而是在完成對自增加值插入的SQL語句後釋放,要等待其釋放才能進行後續操做。好比說當表裏有一個auto_increment字段的時候,innoDB會在內存裏保存一個計數器用來記錄auto_increment的值,當插入一個新行數據時,就會用一個表鎖來鎖住這個計數器,直到插入結束。若是大量的併發插入,表鎖會引發SQL堵塞。併發
在5.1.22以後,InnoDB爲了解決自增主鍵鎖表的問題,引入了參數innodb_autoinc_lock_mode,該實現方式是經過輕量級互斥量的增加機制完成的。它是專門用來在使用auto_increment的狀況下調整鎖策略的,目前有三種選擇:性能
插入類型說明:測試
INSERT-LIKE:指全部的插入語句,好比 INSERT、REPLACE、INSERT…SELECT、REPLACE…SELECT,LOAD DATA等
Simple inserts:指在插入前就能肯定插入行數的語句,包括INSERT、REPLACE,不包含INSERT…ON DUPLICATE KEY UPDATE這類語句。
Bulk inserts:指在插入前不能肯定獲得插入行的語句。如INSERT…SELECT,REPLACE…SELECT,LOAD DATA.
Mixed-mode inserts:指其中一部分是自增加的,有一部分是肯定的。
0:經過表鎖的方式進行,也就是全部類型的insert都用AUTO-inc locking。ui
1:默認值,對於simple insert 自增加值的產生使用互斥量對內存中的計數器進行累加操做,對於bulk insert 則仍是使用表鎖的方式進行。spa
2:對全部的insert-like 自增加值的產生使用互斥量機制完成,性能最高,併發插入可能致使自增值不連續,可能會致使Statement 的 Replication 出現不一致,使用該模式,須要用 Row Replication的模式。.net
在mysql5.1.22以前,mysql的INSERT-LIKE語句會在執行整個語句的過程當中使用一個AUTO-INC鎖將表鎖住,直到整個語句結束(而不是事務結束)。所以在使用INSERT…SELECT、INSERT…values(…),values(…)時,LOAD DATA等耗費時間較長的操做時,會將整個表鎖住,而阻塞其餘的insert-like,update等語句。推薦使用程序將這些語句分紅多條語句,一一插入,減小單一時間的鎖表時間。3d
解決:
經過參數innodb_autoinc_lock_mode =1/2解決,並用simple inserts 模式插入。
問題二:自增主鍵不連續
5.1.22後 默認:innodb_autoinc_lock_mode = 1
直接經過分析語句,得到要插入的數量,而後一次性分配足夠的auto_increment id,只會將整個分配的過程鎖住。
root@localhost : test 04:23:28>show variables like 'innodb_autoinc_lock_mode'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_autoinc_lock_mode | 1 | +--------------------------+-------+ 1 row in set (0.00 sec) root@localhost : test 04:23:31>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk; Query OK, 0 rows affected (0.16 sec) root@localhost : test 04:23:35>insert into tmp_auto_inc(talkid) select talkId from talk_dialog limit 10; Query OK, 10 rows affected (0.00 sec) Records: 10 Duplicates: 0 Warnings: 0 root@localhost : test 04:23:39>show create table tmp_auto_inc\G; *************************** 1. row *************************** Table: tmp_auto_inc Create Table: CREATE TABLE `tmp_auto_inc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `talkid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=gbk 1 row in set (0.00 sec)
插入10條記錄,但表的AUTO_INCREMENT=16,再插入一條的時候,表的自增id已是不連續了。
緣由:
參數innodb_autoinc_lock_mode = 1時,每次會「預申請」多餘的id(handler.cc:compute_next_insert_id),而insert執行完成後,會特別將這些預留的id空出,就是特地將預申請後的當前最大id回寫到表中(dict0dict.c:dict_table_autoinc_update_if_greater)。
這個預留的策略是「不夠時多申請幾個」, 實際執行中是分步申請。至於申請幾個,是由當時「已經插入了幾條數據N」決定的。當auto_increment_offset=1時,預申請的個數是 N-1。
因此會發現:插入只有1行時,你看不到這個現象,並不預申請。而當有N>1行時,則須要。多申請的數目爲N-1,所以執行後的自增值爲:1+N+(N-1)。測試中爲10行,則:1+10+9 =20,和 16不一致?緣由是:當插入8行的時候,表的AUTO_INCREMENT已是16了,因此插入10行時,id已經在第8行時預留了,因此直接使用,自增值仍爲16。因此當插入8行的時候,多申請了7個id,即:9,10,11,12,13,14,15。按照例子中的方法插入8~15行,表的AUTO_INCREMENT始終是16
驗證:
插入16行:猜想 預申請的id:1+16+(16-1)= 32,即:AUTO_INCREMENT=32
root@localhost : test 04:55:45>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk; Query OK, 0 rows affected (0.17 sec) root@localhost : test 04:55:48>insert into tmp_auto_inc(talkid) select talkId from sns_talk_dialog limit 16; Query OK, 16 rows affected (0.00 sec) Records: 16 Duplicates: 0 Warnings: 0 root@localhost : test 04:55:50>show create table tmp_auto_inc\G; *************************** 1. row *************************** Table: tmp_auto_inc Create Table: CREATE TABLE `tmp_auto_inc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `talkid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=gbk 1 row in set (0.00 sec)
和猜想的同樣,自增id到了32。因此當插入16行的時候,多申請了17,18,19...,31 。
因此致使ID不連續的緣由是由於innodb_autoinc_lock_mode = 1時,會多申請id。好處是:一次性分配足夠的auto_increment id,只會將整個分配的過程鎖住。
5.1.22前 默認:innodb_autoinc_lock_mode = 0
root@localhost : test 04:25:12>show variables like 'innodb_autoinc_lock_mode'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_autoinc_lock_mode | 0 | +--------------------------+-------+ 1 row in set (0.00 sec) root@localhost : test 04:25:15>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk; Query OK, 0 rows affected (0.17 sec) root@localhost : test 04:25:17>insert into tmp_auto_inc(talkid) select talkId from talk_dialog limit 10; Query OK, 10 rows affected (0.00 sec) Records: 10 Duplicates: 0 Warnings: 0 root@localhost : test 04:25:21>show create table tmp_auto_inc\G; *************************** 1. row *************************** Table: tmp_auto_inc Create Table: CREATE TABLE `tmp_auto_inc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `talkid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=gbk 1 row in set (0.00 sec)
插入10條記錄,但表的AUTO_INCREMENT=11,再插入一條的時候,表的自增id仍是連續的。
innodb_autoinc_lock_mode = 2 和 innodb_autoinc_lock_mode = 1 的測試狀況同樣。但該模式下是來一個分配一個,而不會鎖表,只會鎖住分配id的過程,和1的區別在於,不會預分配多個,這種方式併發性最高。可是在replication中當binlog_format爲statement-based時存在問題
解決:
儘可能讓主鍵ID沒有業務意義,或則使用simple inserts模式插入。
結論:
當innodb_autoinc_lock_mode爲0時候, 自增id都會連續,可是會出現表鎖的狀況,解決該問題能夠把innodb_autoinc_lock_mode 設置爲1,甚至是2。會提升性能,可是會在必定的條件下致使自增id不連續。
總結:
經過上面2個問題的說明,自增主鍵會產生表鎖,從而引起問題;自增主鍵有業務意義,不連續的主鍵致使主從主鍵不一致到出現問題。對於simple inserts 的插入類型,上面的問題都不會出現。對於Bulk inserts的插入類型,會出現上述的問題。
更多信息:
http://dinglin.iteye.com/blog/1279536
http://hi.baidu.com/thinkinginlamp/item/a0320c82233c6c2a100ef3d0
http://dev.mysql.com/doc/refman/5.5/en/innodb-auto-increment-handling.html