MySQL中如何讲数据添加至table->record

发现很多人在问我在进行insert 操作的时候,其中的 ha_innobase::write_row的参数是table->record[0], 那么此时record[0]中是什么时候将各个field中的数据写入到该record[0]缓存中呢?下面是insert 操作时候的call stack

从上面可以看出,handler::ha_write_row的时候其会调用innodb的write_row来进行真正的数据写人。同时,在Innodb内部会将由每个field所表示的数据的逻辑格式转为innodb接受的物理格式。该项操作由row_mysql_store_col_in_innobase_format 函数来完成。

在fill_record_n_invoke_before_triggers函数中会变量每个Field和Item 从而将由Item所标识的插入的值保存至其对应的Field中。 该项功能由fill_record函数来完成。以int类型为例

insert into xxx values (1,xxx);

此时会将1所对应的Item_long这个对象中获取到其值,int_val,并将该值保存至其Field类型的对象中。

而经过fill_record函数后,那么此时table->record[0]中的数据已经发生变化,变为由各个列中的数据构成了。

bool fill_record(THD *thd, TABLE *table, const mem_root_deque<Item *> &fields,
                 const mem_root_deque<Item *> &values, MY_BITMAP *bitmap,
                 MY_BITMAP *insert_into_fields_bitmap,
                 bool raise_autoinc_has_expl_non_null_val) {
  DBUG_TRACE;
  assert(CountVisibleFields(fields) == CountVisibleFields(values));
  /*
    In case when TABLE object comes to fill_record() from Table Cache it
    should have autoinc_field_has_explicit_non_null_value flag set to false.
    In case when TABLE object comes to fill_record() after processing
    previous row this flag should be reset to false by caller.
    Code which implements LOAD DATA is the exception to the above rule
    as it calls fill_record() to handle SET clause, after values for
    the columns directly coming from loaded from file are set and thus
    autoinc_field_has_explicit_non_null_value possibly set to true.
  */
  assert(table->autoinc_field_has_explicit_non_null_value == false ||
         (raise_autoinc_has_expl_non_null_val &&
          thd->lex->sql_command == SQLCOM_LOAD));
  auto value_it = VisibleFields(values).begin();
  for (Item *fld : VisibleFields(fields)) {
    Item_field *const field = fld->field_for_view_update();
    assert(field != nullptr && field->table_ref->table == table);
    Field *const rfield = field->field;
    Item *value = *value_it++;
    /* If bitmap over wanted fields are set, skip non marked fields. */
    if (bitmap && !bitmap_is_set(bitmap, rfield->field_index())) continue;
    bitmap_set_bit(table->fields_set_during_insert, rfield->field_index());
    if (insert_into_fields_bitmap)
      bitmap_set_bit(insert_into_fields_bitmap, rfield->field_index());
    /* Generated columns will be filled after all base columns are done. */
    if (rfield->is_gcol()) continue;
    if (raise_autoinc_has_expl_non_null_val &&
        rfield == table->next_number_field)
      table->autoinc_field_has_explicit_non_null_value = true;
    /*
      We handle errors from save_in_field() by first checking the return
      value and then testing thd->is_error(). thd->is_error() can be set
      even when save_in_field() does not return a negative value.
      @todo save_in_field returns an enum which should never be a negative
      value. We should change this test to check for correct enum value.
      The below call can reset TABLE::autoinc_field_has_explicit_non_null_value
      flag depending on value provided (for details please see
      set_field_to_null_with_conversions()). So evaluation of this flag can’t
      be moved outside of fill_record(), to be done once per statement.
    */
    if (value->save_in_field(rfield, false) < 0) {
      my_error(ER_UNKNOWN_ERROR, MYF(0));
      return true;
    }
    if (thd->is_error()) return true;
  }
  if (table->has_gcol() &&
      update_generated_write_fields(bitmap ? bitmap : table->write_set, table))
    return true;
  /*
    TABLE::autoinc_field_has_explicit_non_null_value should not be set to
    true in raise_autoinc_has_expl_non_null_val == false mode.
  */
  assert(table->autoinc_field_has_explicit_non_null_value == false ||
         raise_autoinc_has_expl_non_null_val);
  return thd->is_error();
}

由fill_record函数中,我们并未发现其有操作table->record[0]的代码,那么Field又是如何与table->record[0]发生关联呢?使得我们value->save_in_field函数中的操作可以之间写人到table->record[0]中呢?总要又一个地方使得Field和Table中的record[0]发生关联?从而使得对应Field中的ptr的操作可以直接反映到对Table中的record[0]的操作。那么这个地方在哪里呢?答案在:

int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias,
                          uint db_stat, uint prgflag, uint ha_open_flags,
                          TABLE *outparam, bool is_create_table,
                          const dd::Table *table_def_param)
这个函数中。 下面我们就看看其是如何发生关联的。
首先在该函数中会在outparam创建一个新的Table对象

然后对该outparam进行初始,其中一项是分配record内存并将其赋值给outparam中的record.

而后,创建N+1个Field对象并将其复制给outparam中的field:

if (!(field_ptr = root->ArrayAlloc<Field *>(share->fields + 1)))
goto err; /* purecov: inspected */

outparam->field = field_ptr;
record = (uchar *)outparam->record[0] – 1; /* Fieldstart = 1 */
outparam->null_flags = (uchar *)record + 1;

然后对outparam中的field进行复制(由table cache中)mysql中的注释已经说明了一切:

  /*
    We will create fields by cloning TABLE_SHARE’s fields; then we will need
    to make all new fields’ pointers point into the new TABLE’s record[0], by
    applying an offset to them.
    Calculate the “source” offset depending on table type:
    – For non-internal temporary tables, source is share->default_values
    – For internal tables, source is first TABLE’s record[0], which
    happens to be created in same memory block as share->default_values, with
    offset 2 * share->rec_buff_length (see create_tmp_table()).
  */

“make all new fields’ pointers point into the new TABLE’s record[0]” 这样就把每个field指向了record[0],因为可以获取到每个field的类型和数据的长度,因而在后续的fill_record函数中我们对Field的store操作,其实质上也是对record[0]的操作。当我们完成对应所有Field的store操作后,那么我们在reocd[0]中或获得了每个列的数据。

record[0] memory layout:

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

Field1 | Field2 | … | FieldN


  /* Setup copy of fields from share, but use the right alias and record */
  for (i = 0; i < share->fields; i++, field_ptr++) {
    Field *new_field = share->field[i]->clone(root);
    *field_ptr = new_field;
    if (new_field == nullptr) goto err;
    new_field->init(outparam);
    new_field->move_field_offset(move_offset);
    /*
       Initialize Field::pack_length() number of bytes for new_field->ptr
       only if there are no default values for the field.
    */
    if (!has_default_values) new_field->reset();
    /* Check if FTS_DOC_ID column is present in the table */
    if (outparam->file &&
        (outparam->file->ha_table_flags() & HA_CAN_FULLTEXT_EXT) &&
        !strcmp(outparam->field[i]->field_name, FTS_DOC_ID_COL_NAME))
      fts_doc_id_field = new_field;
  }
  (*field_ptr) = nullptr;  // End marker