天天看點

[MySQL源碼] Innodb如何處理auto_inc值

——————————————————–

ha_innobase::write_row是向innodb寫入記錄的函數,進入函數時自增列的值還沒被設定(如何是null的話),會調用handler::update_auto_increment來擷取并更新自增列,然後再調用row_insert_for_mysql來實際插入記錄。我們的精力主要集中在update_auto_increment及其調用的函數上。

先了解下handler的幾個跟自增列相關的成員變量(根據注釋及gdb推測):

 a. ulonglong next_insert_id;  

下一個插入自增列的值,當一次插入多行記錄時(例如,insert select 操作),第一個沒有指定自增列值的記錄會從get_auto_increment函數擷取的值并指派給next_insert_id,這樣對于剩下的記錄就可以直接使用next_insert_id(每次都修改該值)。

b. ulonglong insert_id_for_cur_row; 

目前記錄的insert id 。第一次成功插入後,這個值被存儲到thd::first_successful_insert_id_in_cur_stmt

c. discrete_interval auto_inc_interval_for_cur_row;

從get_auto_increment函數擷取的insert id區間。

discrete_interval 結構體又包含三個值

interval_min  區間的最小值

interval_values 區間内可用自增值的個數

interval_max 最大值

d.  uint auto_inc_intervals_count;

目前插入語句保留的自增區間數

e.  estimation_rows_to_insert

估計将要插入的記錄數,在函數handler::ha_start_bulk_insert()中被設定,值為0表示未知。

//////////////////////////////////////////////////////////////////////////////////////////////////////

下面來分析下handler::update_auto_increment主要流程

1.首先判斷自增列是否已經指派,或者是否不可以為null&&sql_mode為mode_no_auto_value_on_zero時,不做處理

if ((nr= table->next_number_field->val_int()) != 0 ||

      (table->auto_increment_field_not_null &&

      thd->variables.sql_mode & mode_no_auto_value_on_zero)){

       handler::adjust_next_insert_id_after_explicit_value    // 根據nr和offset設定下一個自增值next_insert_id

       insert_id_for_cur_row= 0;

       dbug_return(0);

}

2.當預取的insert id區間用完時,需要取更多的insert id。

 if ((nr= next_insert_id) >= auto_inc_interval_for_cur_row.maximum()) 

{

a.如果預取了下一個區間,則使用下一個區間的server id。

      nr= forced->minimum();

      nb_reserved_values= forced->values();

b.否則:

(1)确定需要自增值的數目(nb_desired_values)

當沒有預留值,且估值大于0時(auto_inc_intervals_count == 0) && (estimation_rows_to_insert > 0),設定nb_desired_values=estimation_rows_to_insert

否則:   (例如insert into t values (null,12),(98,51),(null,67),第3個記錄會走到這個邏輯)

–auto_inc_intervals_count <= auto_inc_default_nb_max_bits時

          nb_desired_values= auto_inc_default_nb_rows *

            (1 << auto_inc_intervals_count);

          set_if_smaller(nb_desired_values, auto_inc_default_nb_max);

           nb_desired_values值為1*(1<< auto_inc_intervals_count)且不大于auto_inc_default_nb_max(65535)

–auto_inc_intervals_count> auto_inc_default_nb_max_bits時

      nb_desired_values值為最大值65535

實際上,對于innodb而言,請求的數量可能跟實際獲得的自增值數量不同,這取決于trx->n_autoinc_rows

每寫完一次記錄後, trx->n_autoinc_rows 會被減1(在函數ha_innobase::write_row 中)

例如,對于sql:insert into t values (null,12),(98,51),(null,67),(120,44),(null,123);

假設目前值為90

第一次調用update_auto_increment時,請求保留5個值

trx->n_autoinc_rows=5,保留5個值

{interval_min = 90, interval_values = 5, interval_max = 95, next = 0x0}

第二次調用update_auto_increment時,使用98

trx->n_autoinc_rows=4

第三次調用update_auto_increment時,請求保留2個值

trx->n_autoinc_rows=3, 保留3個值

{interval_min = 99, interval_values = 3, interval_max = 102, next = 0x0}

prebuilt->trx->n_autoinc_rows

第四次調用update_auto_increment時,使用120

trx->n_autoinc_rows==2

第五次調用update_auto_increment時,請求保留4個值

trx->n_autoinc_rows=1,保留1個值

{interval_min = 121, interval_values = 1, interval_max = 122, next = 0x0}

(2)調用ha_innobase::get_auto_increment擷取自增區間,更新table->autoinc (函數後續詳解)

(3)nr= compute_next_insert_id(nr-1, variables);  根據offset等配置項計算nr

(4)當(table->s->next_number_keypart == 0時,設定append=true

3.table->next_number_field->store((longlong) nr, true)

4.append為true時(為false表示使用的是之前預取的值,無需執行如下步驟)

設定目前auto_inc_interval_for_cur_row

auto_inc_intervals_count++

如果為statement模式,則記錄該自增值到binlog

5.insert_id_for_cur_row= nr

6.set_next_insert_id(compute_next_insert_id(nr, variables)); 設定next_insert_id的值

///////////////////////////////////////////////////////////////////////////////////////////////////

ha_innobase::get_auto_increment是實際配置設定自增值的主要函數。流程如下:

1.調用函數innobase_get_autoinc

a.

ha_innobase::innobase_lock_autoinc

根據不同的autoinc_lock_mode選擇加鎖模式,我們的環境下這個值都是1,是以選擇的case是autoinc_new_style_locking

注意,以load 或者 insert ..select的方式插入資料時會回退到autoinc_old_style_locking

dict_table_autoinc_lock->mutex_enter(&table->autoinc_mutex);   //擷取autoinc mutex

再判斷是否有别的事務已經擷取或在等待autoinc lock,如果是的話,解除mutex并回退到autoinc_old_style_locking,否則break,并傳回。

在old style locking模式下,調用row_lock_table_autoinc_for_mysql加auto_inc lock(調用lock_table函數加lock_auto_inc鎖),随後如果成功了,再調用dict_table_autoinc_lock(prebuilt->table)加autoinc mutex 

在lock_table->lock_table_create函數裡會對table->n_waiting_or_granted_auto_inc_locks++

b.

調用dict_table_autoinc_read(prebuilt->table)擷取table->autoinc值

2.

如果tx->n_autoinc_row=0,設定該值為nb_desired_values或者1(如果nb_desired_values=0),然後set_if_bigger(*first_value, autoinc),将first_value設定為目前的table->autoinc值

如果prebuilt->autoinc_last_value == 0,也就是不在一次muti-insert的中間,也設定set_if_bigger(*first_value, autoinc)

*nb_reserved_values = trx->n_autoinc_rows;

3.當不是使用autoinc_old_style_locking時

a.計算目前值current和需要保留的區間長度need

b.調用next_value = innobase_next_autoinc函數計算保留區間的最後一個值  (bug#61209 fix點)

c.prebuilt->autoinc_last_value = next_value

d.更新table->autoinc的值(dict_table_autoinc_update_if_greater)

4.

prebuilt->autoinc_offset = offset;

prebuilt->autoinc_increment = increment;

dict_table_autoinc_unlock(prebuilt->table)->mutex_exit(&table->autoinc_mutex);    //解除mutex

繼續閱讀