MySQL auto_increment的坑
背景:
Innodb引擎使用B_tree結構保存表數據,這樣就需要一個唯一鍵表示每一行記錄(比如二級索引記錄引用)。
Innodb表定義中處理主鍵的邏輯是:
1.如果表定義了主鍵,就使用主鍵唯一定位一條記錄
2.如果沒有定義主鍵,Innodb就生成一個全局唯一的rowid來定位一條記錄
auto_increment的由來:
1.Innodb強烈推薦在設計表中自定義一個主鍵,因為rowid是全局唯一的,所以如果有很多表沒有定義主鍵,就會在生成rowid上產生爭用。
/* Dictionary system struct */ struct dict_sys_struct{ mutex_t mutex; row_id_t row_id; ...... }
row_id由mutex保護,並在每次checkpoint的時候,寫入到數據字典的文件頭。
2.當用戶自定義了主鍵後,由於大部分實際應用部署的分布式,所以主鍵值的生成上,采用集中式的方式,更容易實現唯一性,所以auto_increment非常合適。
auto_increment也帶來兩個好處:
1. auto_increment的值是表級別的,不會在db級別上產生爭用
2. 由於auto_increment的順序性,減少了隨機讀的可能,保證了寫入的page的緩沖命中。(不可否認,寫入的並發足夠大時,會產生熱點塊的爭用)
auto_increment引起的bug:
環境:MySQL 5.6.16版本, binlog_format=row
case復現:
create table test.kkk ( c int(11) default null, id int(11) not null auto_increment, d int(11) default null, primary key (id), unique key d (d) ) engine=innodb default charset=latin1; insert into test.kkk values(5, 27,4); replace into test.kkk(c, id, d) values(6, 35, 4); commit;
show create table時: 主庫:auto_increment=36 備庫:auto_increment=28
當進行主備切換後,導致主鍵沖突,slave恢復異常。
同樣insert on duplication update 語句同樣存在這樣的問題。
aliyun rds分支bug修復
問題的原因:Innodb對於auto_increment的處理,當語句是insert時,會進行遞增,而update,delete語句則不更新。
當replace語句在主庫的執行時:
1. 先按照insert語句執行,發現uk沖突。
2. 演變成update語句進行更新。
這樣在主庫,雖然insert失敗,但auto_increment也遞增上去了。但到備庫,row格式下,只產生了一個update row event,
備庫無法知道主庫是一個replace語句,而且insert還失敗了, 所以auto_increment在備庫沒有遞增。
修復方式:在備庫,對於update進行auto_increment遞增,可能會產生副作用,即auto_increment的浪費,但不會產生主鍵沖突。
那些年經歷的auto_increment坑:
1. 實例重啟,主鍵沖突:
內存中的autoinc值,在系統重啟後,使用select max(id) from table來初始化。所以,如果你設計的業務表,存在delete操作,那麽一旦你的實例crash過,重啟後,可能會復用以前使用過的id值。如果你需要持續對這個表進行邏輯備份,那麽就可能會碰到主鍵沖突的問題。
2. load file阻塞:
在設置innodb_autoinc_lock_mode=1的時候,MySQL為了維護單個statement語句的id連續性,當不確定插入條數的時候,會在語句整個執行過程中
持有LOCK_AUTO_INC, /* locks the auto-inc counter of a table in an exclusive mode */
這個鎖是表級別的,使用互斥模式。
所以,在繁忙的表上,如果要導入數據,小心可能阻塞正常的業務寫入,並發寫入在這個時候也會阻塞的。
MySQL auto_increment的坑