MySQL auto_increment實現

運維的時候,經常遇到auto_increment的疑惑:

  1. 機器異常crash,重啓後id回退的問題
  2. 性能考慮,每次獲取肯定不會持久化,內存中取值,statement複製如何保證主備一致
  3. 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產生阻塞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章