http://www.cnblogs.com/xpchild/p/3825309.html
運維的時候,經常遇到auto_increment的疑惑:
- 機器異常crash,重啟后id回退的問題
- 性能考慮,每次獲取肯定不會持久化,內存中取值,statement復制如何保證主備一致
- id的取值受binlog的保護嗎
1. auto_increment相關的參數控制
1.1 innodb_autoinc_lock_mode
0: 每一個statement獲取一個排他lock,直到statement結束,保證statement執行過程的id是連續的。
1: 單條確定insert影響的條數的時候,使用mutex。如果是insert select,load data這樣的,使用排他lock。
2: 多條statement產生的id會穿插在一起,如果是statement復制,會產生不一致的情況。
1.2
auto_increment_increment
auto_increment_offset
控制自增的起始值和interval
2. auto_increment相關的數據結構
1. 鎖模式中LOCK_AUTO_INC,即auto_increment的表鎖。
/* Basic lock modes */ enum lock_mode { LOCK_IS = 0, /* intention shared */ LOCK_IX, /* intention exclusive */ LOCK_S, /* shared */ LOCK_X, /* exclusive */ LOCK_AUTO_INC, /* locks the auto-inc counter of a table in an exclusive mode */ LOCK_NONE, /* this is used elsewhere to note consistent read */ LOCK_NUM = LOCK_NONE/* number of lock modes */ };
2. dict_table_t: innodb表定義
lock_t* autoinc_lock; 表鎖 mutex_t autoinc_mutex; mutex鎖 ib_uint64_t autoinc; 自增值 ulong n_waiting_or_granted_auto_inc_locks; 等待自增表鎖的隊列數 const trx_t* autoinc_trx; hold自增表鎖的事務
3. trx_t: 事務結構
ulint n_autoinc_rows; statement插入的行數 ib_vector_t* autoinc_locks; 持有的自增lock
4. handler:table的innodb引擎句柄
ulonglong next_insert_id; 下次插入的id ulonglong insert_id_for_cur_row; 當前插入的id Discrete_interval auto_inc_interval_for_cur_row; 緩存,一次申請一個區間,緩存在server層。減少對innodb的調用 uint auto_inc_intervals_count; 向innodb申請id的interval。按照[1, 2, 4, 8, 16]遞增。 最多1<<16 -1
注意:handler里的這些變量,只在一個語句下有效,語句結束就清理掉了。
3. 測試case
create table pp( id int primary key auto_increment, name varchar(100));
session1 : insert into pp(name) values('xx');
session2 : insert into pp(name) values('xx'),('xx'),('xx'),('xx')
session3 : insert into pp(name) select name from pp;
4. auto_increment的實現原理
4.2 鎖的解釋
根據鎖持有的時間粒度,分為
1. 內存級別:類似mutex,很快釋放
2. 語句級別:statement結束,釋放
3. 事務級別:transaction提交或者回滾才釋放
4. 會話級別:session級別,連接斷開才釋放
這里,session1和session2都是確定insert的條數,所以使用mutex分配固定的id。而session3未知,所以為了保證這一個statement的id是連續的,拿到一個lock,維持到statement結束才釋放。
所以,為了提高並發量,鎖持有的粒度越小越好。
4.3 緩存的解釋
針對一個statement,預分配id值,減少對innodb的請求,也相應減少持有鎖。
5. 測試細節
5.1 第一次執行
根據select max(id) from pp:獲取autoinc的初始值
這樣也就解釋了文章開頭的第一個疑惑,為什么機器crash了,id會回退。
簡單函數棧:
ha_innobase::open
innobase_initialize_autoinc
5.2 session 1
1. 首先 持有mutex,獲取autoinc
2. 因為insert的條數是1條,計算新的autoinc並更新到dict_table_t中,然后釋放mutex結束
簡單函數棧
handler::update_auto_increment
ha_innobase::get_auto_increment
ha_innobase::innobase_lock_autoinc
mutex_enter(&table->autoinc_mutex);
dict_table_autoinc_update_if_greater
5.3 session 2
1. 因為insert的條數是4條,所以前面的步驟都類似於session1,但計算完成新的autoinc為5,並更新dict_table_t.
2. 因為cache了[3,4,5],所以后面的三條insert,都在本地緩存中獲取,不再請求innodb。
5.4 session 3
1. 因為不確定insert的條數,所以在語句的整個執行期間,持有lock。
2. 語句結束時,statement commit的時候釋放
3. 第一次申請1個,第二次申請2個,第三次申請4個,共申請了3次。
簡單函數棧:
handler::update_auto_increment
ha_innobase::get_auto_increment
row_lock_table_autoinc_for_mysql
trans_commit_stmt
row_unlock_table_autoinc_for_mysql
語句結束后, 清理語句級的環境
ha_release_auto_increment
insert_id_for_cur_row= 0; 當前語句的insert id設置為0
auto_inc_interval_for_cur_row.replace(0, 0, 0); 預分配的清空
auto_inc_intervals_count= 0; 預分配的迭代數也清0
table->in_use->auto_inc_intervals_forced.empty(); 清理鏈表
6. 警告:
1. 如果你的表是insert+delete的模式,你會發現重啟了后,id被復用了,小心,被坑過的說。
2. 如果表上有自增鍵,insert select,load file,會對insert產生阻塞。
7. 思考:
1. 分布式的全局唯一遞增(不保證連續) 怎么實現。 這是分布式系統都需要解決的問題!