小A正在balabala寫代碼呢,DBA小B忽然發來了一條消息,「快看看你的用戶特定信息表T,裏面的主鍵,也就是自增id,都到16億了,這纔多久,在這樣下去過不了多久主鍵就要超出範圍了,插入就會失敗,balabala......」mysql
我記得沒有這麼多,最多1k多萬,count了下,果真是1100萬。原來運維是經過auto_increment
那個值看的,就是說,表中有大量的刪除插入操做,可是我大部分狀況都是更新的,怎麼會這樣?sql
這張表是一個簡單的接口服務在使用,天天大數據會統計一大批信息,而後推送給小A,小A將信息更新到數據庫中,若是是新數據就插入,舊數據就更新以前的數據,對外接口就只有查詢了。數據庫
很快,小A就排查了一遍本身的代碼,沒有刪除的地方,也沒有主動插入、更新id的地方,怎麼會這樣呢?難道是小B的緣由,也不太可能,DBA那邊兒管理不少表,有問題的話早爆出來了,但問題在我這裏哪裏也沒頭緒。安全
小A又仔細觀察了這1000多萬已有的數據,將插入時間、id做爲主要觀察字段,很快,發現了個問題,天天第一條插入的數據老是比前一天多1000多萬,有時候遞增的多,有時候遞增的少,小A又將矛頭指向了DBA小B,將問題又給小B描述了一遍。併發
小B問了小A,「你是是否是用了REPLACE INTO ...
語句」,這是怎麼回事呢,原來REPLACE INTO ...
會對主鍵有影響。運維
REPLACE INTO ...
對主鍵的影響假設有一張表t1
:性能
CREATE TABLE `t1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID,自增', `uid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用戶uid', `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶暱稱', PRIMARY KEY (`id`), UNIQUE KEY `u_idx_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='測試replace into';
若是新建這張表,執行下面的語句,最後的數據記錄如何呢?測試
insert into t1 values(NULL, 100, "test1"),(NULL, 101, "test2"); replace into t1 values(NULL, 100, "test3");
原來,REPLACE INTO ...
每次插入的時候若是惟一索引對應的數據已經存在,會刪除原數據,而後從新插入新的數據,這也就致使id會增大,但實際預期多是更新那條數據。大數據
小A說:「我知道replace是這樣,全部既沒有用它」,但仍是又排查了一遍,確實不是本身的問題,沒有使用REPLACE INTO ...
,ui
小A又雙叒叕仔細的排查了一遍,仍是沒發現問題,就讓小B查下binlog日誌,看看是否是有什麼奇怪的地方,查了以後仍是沒發現問題,確實存在跳躍的狀況,但並無實質性的問題。
下圖中@1
的值對應的是自增主鍵id
,用(@2, @3)
做爲惟一索引
後來過了好久,小B給小A指了個方向,小A開始懷疑本身的插入更新語句INSERT ... ON DUPLICATE KEY UPDATE ...
了,查了許久,果真是這裏除了問題。
INSERT ... ON DUPLICATE KEY UPDATE ...
對主鍵的影響這個語句跟REPLACE INTO ...
相似,不過他並不會變動該條記錄的主鍵,仍是上面t1
這張表,咱們執行下面的語句,執行完結果是什麼呢?
insert into t1 values(NULL, 100, "test4") on duplicate key update name = values(name);
沒錯,跟小A預想的同樣,主鍵並無增長,並且name
字段已經更新爲想要的了,可是執行結果有條提示,引發了小A的注意
No errors; 2 rows affected, taking 10.7ms
明明更新了一條數據,爲何這裏的影響記錄條數是2呢?小A,又看了下目前表中的auto_increment
CREATE TABLE `t1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID,自增', `uid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用戶uid', `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶暱稱', PRIMARY KEY (`id`), UNIQUE KEY `u_idx_uid` (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='測試replace into';
居然是5`,這裏本應該是4的。
也就是說,上面的語句,會跟REPLACE INTO ...
相似的會將自增ID加1,但實際記錄沒有加,這是爲何呢?
查了資料以後,小A得知,原來,mysql主鍵自增有個參數innodb_autoinc_lock_mode
,他有三種可能只0
,1
,2
,mysql5.1以後加入的,默認值是1
,以前的版本能夠看作都是0
。
可使用下面的語句看當前是哪一種模式
select @@innodb_autoinc_lock_mode;
小A使用的數據庫默認值也是1
,當作簡單插入(能夠肯定插入行數)的時候,直接將auto_increment
加1,而不會去鎖表,這也就提升了性能。當插入的語句相似insert into select ...
這種複雜語句的時候,提早不知道插入的行數,這個時候就要要鎖表(一個名爲AUTO_INC
的特殊表鎖)了,這樣auto_increment
纔是準確的,等待語句結束的時候才釋放鎖。還有一種稱爲Mixed-mode inserts
的插入,好比INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d')
,其中一部分明確指定了自增主鍵值,一部分未指定,還有咱們這裏討論的INSERT ... ON DUPLICATE KEY UPDATE ...
也屬於這種,這個時候會分析語句,而後按儘量多的狀況去分配auto_increment
id,這個要怎麼理解呢,我看下面這個例子:
truncate table t1; insert into t1 values(NULL, 100, "test1"),(NULL, 101, "test2"),(NULL, 102, "test2"),(NULL, 103, "test2"),(NULL, 104, "test2"),(NULL, 105, "test2"); -- 此時數據表下一個自增id是7 delete from t1 where id in (2,3,4); -- 此時數據表只剩1,5,6了,自增id仍是7 insert into t1 values(2, 106, "test1"),(NULL, 107, "test2"),(3, 108, "test2"); -- 這裏的自增id是多少呢?
上面的例子執行完以後表的下一個自增id是10,你理解對了嗎,由於最後一條執行的是一個Mixed-mode inserts
語句,innoDB會分析語句,而後分配三個id,此時下一個id就是10了,但分配的三個id並不必定都使用。此處 @老是遲到 多謝指出,看官方文檔理解錯了
模式0
的話就是無論什麼狀況都是加上表鎖,等語句執行完成的時候在釋放,若是真的添加了記錄,將auto_increment
加1。
至於模式2
,什麼狀況都不加AUTO_INC
鎖,存在安全問題,當binlog
格式設置爲Statement
模式的時候,從庫同步的時候,執行結果可能跟主庫不一致,問題很大。由於可能有一個複雜插入,還在執行呢,另一個插入就來了,恢復的時候是一條條來執行的,就不能重現這種併發問題,致使記錄id可能對不上。
至此,id跳躍的問題算是分析完了,因爲innodb_autoinc_lock_mode
值是1,INSERT ... ON DUPLICATE KEY UPDATE ...
是簡單的語句,預先就能夠計算出影響的行數,因此不論是否更新,這裏都將auto_increment
加1(多行的話大於1)。
若是將innodb_autoinc_lock_mode
值改成0
,再次執行INSERT ... ON DUPLICATE KEY UPDATE ...
的話,你會發現auto_increment
並無增長,由於這種模式直接加了AUTO_INC
鎖,執行完語句的時候釋放,發現沒有增長行數的話,不會增長自增id的。
INSERT ... ON DUPLICATE KEY UPDATE ...
影響的行數是1爲何返回2?爲何會這樣呢,按理說影響行數就是1啊,看看官方文檔的說明
With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values
官方明確說明了,插入影響1行,更新影響2行,0的話就是存在且更新先後值同樣。是否是很很差理解?
其實,你要這樣想就行了,這是爲了區分究竟是插入了仍是更新了,返回1表示插入成功,2表示更新成功。
將innodb_autoinc_lock_mode
設置爲0確定能夠解決問題,但這樣的話,插入的併發性可能會受很大影響,所以小A本身想着DBA也不會贊成。通過考慮,目前準備了兩種較爲可能的解決方案:
修改業務邏輯,將INSERT ... ON DUPLICATE KEY UPDATE ...
語句拆開,先去查詢,而後去更新,這樣就能夠保證主鍵不會不受控制的增大,但增長了複雜性,原來的一次請求可能變爲兩次,先查詢有沒有,而後去更新。
刪除自增主鍵,讓惟一索引來作主鍵,這樣子基本不用作什麼變更,只要肯定目前的自增主鍵沒有實際的用處便可,這樣的話,插入刪除的時候可能會影響效率,但對於查詢多的狀況來講,小A比較兩種以後更願意選擇後者。
其實INSERT ... ON DUPLICATE KEY UPDATE ...
這個影響行數是2的,小A很早就發現了,只是沒有保持好奇心,不覺得然罷了,沒有深究其中的問題,這深究就起來會帶出來一大串新知識,挺好,看來小A仍是要對外界保持好奇心,保持敏感,這樣纔會有進步。