MySQL InnoDB Startup分析

以下分析基于MySQL 8.0.30.

本文主要用来介绍下MySQL InnoDB启动过程中的相关流程。从MySQL 架构可以看出,InnoDB作为MySQL中的一个存储引擎插件,其会在MySQL Server启动的过程中由MySQL Server作为调用者来负责启动其所安装的各类插件,即:InnoDB也会在MySQL启动过程中由MySQL Server来负责InnoDB的启动,而整个MySQL实例在启动入口为 mysqld_main函数,在该函数中由process_bootstrap函数来启动相应的线程来完成。

bool error = bootstrap::run_bootstrap_thread(init_file_name, init_file, nullptr, system_thread)

  • 基本情况介绍

首先,会完成对于字典表的初始化。从MySQL的源码中我们可以知道:其在处理第一次启动和非第一次启动(或者说升级)这两者是有着不同的逻辑,主要是基于如下的三个设计原则:

1:两步处理(two-step process):字典初始化被实现为一个两步处理过程。首先,构建脚手架(scaffolding)以准备与持久存储同步,然后进行实际同步。这样做的方式取决于上下文,因此,对于首次启动和随后的重新启动采用不同的处理步骤。[备注:这里的脚手架,指用来辅助完成初始化操作的相关函数和模块,就像我们盖大楼时候搭建的脚手架一样,起到辅助功能,其用来执行相应的SQL语句完成相应系统表的创建等工作]

2:使用 SQL(Use SQL):初始化过程使用 SQL 来构建脚手架。这意味着我们执行 SQL 语句来创建字典表。由于这是在物理表要么尚不存在,要么未知的阶段完成的,因此,只在首次启动时创建表的物理对应部分时候(即所谓的创建物理表),执行该建表DDL语句。[备注:这里我们可以在源码目录下面看到有创建系统表的相关SQL脚本存在, scripts目录下,初始化数据库时候执行该sql脚本,即执行init database时候。

1. mysql_system_tables.sql – Creates the system tables

2. mysql_system_tables_fix.sql – Updates the system table

3. mysql_system_tables_data_fix.sql – Fills the system tables with meta data

4. mysql_sys_schema.sql – Create and/or updates the sys schema

static bool handle_bootstrap_impl(handle_bootstrap_args *args) {
DBUG_TRACE;

THD *thd = args->m_thd;
int rc;

thd->thread_stack = (char *)&thd;
thd->security_context()->assign_user(STRING_WITH_LEN(“boot”));
thd->security_context()->skip_grants(“”, “”);

/*
Make the “client” handle multiple results. This is necessary
to enable stored procedures with SELECTs and Dynamic SQL
in init-file.
*/
thd->get_protocol_classic()->add_client_capability(CLIENT_MULTI_RESULTS);

thd->init_query_mem_roots();

if (opt_initialize) {
/*
During –initialize, the server will also read SQL statements from a
file submitted with –init-file. While processing the compiled-in
statements, DD table access is permitted. This is needed as a short
term solution to allow SRS data to be entered by INSERT statements
instead of CREATE statements.
*/
assert(thd->system_thread == SYSTEM_THREAD_SERVER_INITIALIZE);

/*
The server must avoid logging compiled statements into the binary log
(and generating GTIDs for them when GTID_MODE is ON) during bootstrap/
initialize procedures.
We disable SQL_LOG_BIN session variable while processing compiled
statements.
*/
const Disable_binlog_guard disable_binlog(thd);
const Disable_sql_log_bin_guard disable_sql_log_bin(thd);
//由cmake时候根据相应的sql文件,生成的文件,例如:mysql_system_tabls.sql
//生成 sql_command_system_tables.h,在该头文件中,使用数组mysql_system_tables
//记录x下相应的要创建表的sql,由下述process_iterator来执行该sql语句
Compiled_in_command_iterator comp_iter;
rc = process_iterator(thd, &comp_iter, true);

3:伪缓存(Fake caching):保证DDL语句最小化,数据字典中的缓存层,同时其也在脚手架阶段提供了统一的行为[这样做的好处是保证一致,访问加速]。这意味着从外部看,字典对象可以从缓存中检索出来。在缓存层下面,在脚手架构建完成之前,对象只是被保留在一个单独的缓冲区中。此时,我们可以开始根据情况使用底层的物理表:

  • 对于首次启动(初始化),我们可以将在脚手架阶段生成的元数据刷新到数据字典表中。[备注:这里可以将脚手架和数据字典缓存当作两层缓存]
  • 对于普通重新启动,我们可以使用脚手架打开物理表,然后将存储在持久存储中的真实元数据与其同步。
  • 对于升级,我们首先基于实际的数据字典表构建脚手架,然后创建目标数据字典表,将元数据从旧表迁移到新表,并最终通过在数据字典表上使用 DML(数据操纵语言)的方式原子地从旧表切换到新表。这意味着我们直接在数据字典表中更新schema id,而不是执行 ‘RENAME TABLE’,后者会自动提交,从而破坏了原子性。

当系统处于第一次启动时候,其初始的相应DD的逻辑如下:(1)准备阶段;首先,在该阶段中通过dict_init()函数来初始化DD引擎(DDSE)。此时,会物理创建相应预定义好的表空间。再者,准备dd::Tablespace对象(2)scaffolding阶段。在该阶段中通过执行相应的SQL语句来创建相应的物理schema,例如:mysql和创建相应的物理系统表。(3)同步阶段,保存相应的DD schema, tablespace, table等元数据。

而对于重新启动时其也包括上述的三个步骤,只是在第二阶段时,其不需要创建物理对象(schema, table),而只是创建相应的元数据信息。具体详细信息可以参照:sql/dd/impl/bootstrap/bootstrap.h中相应注释。

上文我们谈到在启动过程中的三个阶段,第一个阶段是准备阶段,在此阶段中会完成对于ddse的初始化。首先,由innobase_ddse_dict_init函数来完成。

上文我们谈到在启动过程中的三个阶段,第一个阶段是准备阶段,在此阶段中会完成对于ddse的初始化。首先,由innobase_ddse_dict_init函数来完成。

static bool innobase_ddse_dict_init(dict_init_mode_t dict_init_mode,
uint version,
List<const dd::Object_table> *tables,
List<const Plugin_tablespace> *tablespaces);

innobase_ddse_dict_init函数完成如下两个操作:(1)初始化(创建或者打开)相应的innodb相关文件;innobase_init_files()(2)设置相应系统表的定义及相应的表定义对象。

dd::Object_table *innodb_dynamic_metadata =
dd::Object_table::create_object_table();
innodb_dynamic_metadata->set_hidden(true);
dd::Object_table_definition *def =
innodb_dynamic_metadata->target_table_definition();
def->set_table_name(“innodb_dynamic_metadata”);
def->add_field(0, “table_id”, “table_id BIGINT UNSIGNED NOT NULL”);
def->add_field(1, “version”, “version BIGINT UNSIGNED NOT NULL”);
def->add_field(2, “metadata”, “metadata BLOB NOT NULL”);
def->add_index(0, “index_pk”, “PRIMARY KEY (table_id)”);

这些表包括:innodb_dynamic_metadata,innodb_table_stats,innodb_index_stats,innodb_ddl_log。

  • srv_start,InnoDB 启动入口(innobase/srv/srv0start.cc)
   到这里似乎我们仍然未提及InnoDB的启动阶段相关代码或者模块。别着急,接下来我们便会进入InnoDB的启动阶段。上文我们知道 innobase_init_files()函数用来创建或者打开相应的InnoDB相关的文件。通过阅读我们发现在该函数中完成了InnoDB server的启动。
static int innobase_init_files(dict_init_mode_t dict_init_mode,
List<const Plugin_tablespace> *tablespaces) {
DBUG_TRACE;ut_ad(dict_init_mode == DICT_INIT_CREATE_FILES ||
dict_init_mode == DICT_INIT_CHECK_FILES ||
dict_init_mode == DICT_INIT_UPGRADE_57_FILES);

bool create = (dict_init_mode == DICT_INIT_CREATE_FILES);

/* Check if the data files exist or not. */
dberr_t err =
srv_sys_space.check_file_spec(create, MIN_EXPECTED_TABLESPACE_SIZE);

if (err != DB_SUCCESS) {
return innodb_init_abort();
}

srv_is_upgrade_mode = (dict_init_mode == DICT_INIT_UPGRADE_57_FILES);

/* Start the InnoDB server. */
err = srv_start(create);

if (err != DB_SUCCESS) {
return innodb_init_abort();
}

首先检查相应的系统表空间文件是否存在,如果存在则会通过srv_start函数来启动InnoDB server并打开相应的系统表空间

// For upgrade from 5.7, create mysql.ibd
create |= (dict_init_mode == DICT_INIT_UPGRADE_57_FILES);
ret = create ? dd_create_hardcoded(dict_sys_t::s_dict_space_id,
dict_sys_t::s_dd_space_file_name)
: dd_open_hardcoded(dict_sys_t::s_dict_space_id,
dict_sys_t::s_dd_space_file_name);

1) 启动 srv_boot

/** Boots the InnoDB server. */
void srv_boot(void) {
/* Initialize synchronization primitives, memory management, and thread
local storage */

srv_general_init();

/* Initialize this module */

srv_init();
}

该阶段进行相关同步原语,内存管理等等模块初始化。

static void srv_general_init() {
sync_check_init(srv_max_n_threads);
/* Reset the system variables in the recovery module. */
recv_sys_var_init(); //recovery相关系统变量初始化
os_thread_open(); //线程相关结构初始化,
trx_pool_init(); //trx_t的池的创建。new 开辟一内存空间
que_init(); //
row_mysql_init(); //row_drop_list_mutex初始化
undo_spaces_init();//undo表空间初始,trx_sys_undo_spaces
}

/** Initializes the server. */
static void srv_init(void) {
ulint n_sys_threads = 0;
ulint srv_sys_sz = sizeof(*srv_sys);
//各个mutex对象和event对象的创建
/* Create mutex to protect encryption master_key_id. */
{
/* This is defined in ha_innodb.cc and used during create_log_files(), which
we call after calling srv_boot() which defines types of mutexes, so we have
to create this mutex in between the two calls. */
extern ib_mutex_t master_key_id_mutex;

mutex_create(LATCH_ID_MASTER_KEY_ID_MUTEX, &master_key_id_mutex);
}

mutex_create(LATCH_ID_SRV_INNODB_MONITOR, &srv_innodb_monitor_mutex);

ut_d(srv_threads.m_shutdown_cleanup_dbg = os_event_create());

srv_threads.m_master_ready_for_dd_shutdown = os_event_create();

srv_threads.m_purge_coordinator = {};

srv_threads.m_purge_workers_n = srv_n_purge_threads;

srv_threads.m_purge_workers = ut::new_arr_withkey<IB_thread>(
UT_NEW_THIS_FILE_PSI_KEY, ut::Count{srv_threads.m_purge_workers_n});

if (!srv_read_only_mode) {
/* Number of purge threads + master thread */
n_sys_threads = srv_n_purge_threads + 1;

srv_sys_sz += n_sys_threads * sizeof(*srv_sys->sys_threads);
}

srv_threads.m_page_cleaner_coordinator = {};

srv_threads.m_page_cleaner_workers_n = srv_n_page_cleaners;

srv_threads.m_page_cleaner_workers = ut::new_arr_withkey<IB_thread>(
UT_NEW_THIS_FILE_PSI_KEY,
ut::Count{srv_threads.m_page_cleaner_workers_n});

srv_sys = static_cast<srv_sys_t *>(
ut::zalloc_withkey(UT_NEW_THIS_FILE_PSI_KEY, srv_sys_sz));

srv_sys->n_sys_threads = n_sys_threads;

/* Even in read-only mode we flush pages related to intrinsic table
and so mutex creation is needed. */
{
mutex_create(LATCH_ID_SRV_SYS, &srv_sys->mutex);

mutex_create(LATCH_ID_SRV_SYS_TASKS, &srv_sys->tasks_mutex);

srv_sys->sys_threads = (srv_slot_t *)&srv_sys[1];

for (ulint i = 0; i < srv_sys->n_sys_threads; ++i) {
srv_slot_t *slot = &srv_sys->sys_threads[i];

slot->event = os_event_create();

slot->in_use = false;

ut_a(slot->event);
}
//各个events对象创建
srv_error_event = os_event_create();

srv_monitor_event = os_event_create();

srv_buf_dump_event = os_event_create();

buf_flush_event = os_event_create();

buf_flush_tick_event = os_event_create();

UT_LIST_INIT(srv_sys->tasks);
}

srv_buf_resize_event = os_event_create();

ut_d(srv_master_thread_disabled_event = os_event_create());

/* page_zip_stat_per_index_mutex is acquired from:
1. page_zip_compress() (after SYNC_FSP)
2. page_zip_decompress()
3. i_s_cmp_per_index_fill_low() (where SYNC_DICT is acquired)
4. innodb_cmp_per_index_update(), no other latches
since we do not acquire any other latches while holding this mutex,
it can have very low level. We pick SYNC_ANY_LATCH for it. */
mutex_create(LATCH_ID_PAGE_ZIP_STAT_PER_INDEX,
&page_zip_stat_per_index_mutex);

/* Create dummy indexes for infimum and supremum records */
dict_ind_init();

/* Initialize some INFORMATION SCHEMA internal structures */
trx_i_s_cache_init(trx_i_s_cache);

ut_crc32_init();

dict_mem_init();
}

2)初始化 fil_system
扫描系统目录并初始化fil_system对象。
fil_init(innobase_get_open_files_limit());/* This is the default directory for IBD and IBU files. Put it first
in the list of known directories. */
fil_set_scan_dir(MySQL_datadir_path.path());

/* Add –innodb-data-home-dir as a known location for IBD and IBU files
if it is not already there. */
ut_ad(srv_data_home != nullptr && *srv_data_home != ‘\0’);
fil_set_scan_dir(Fil_path::remove_quotes(srv_data_home));

/* Add –innodb-directories as known locations for IBD and IBU files. */
if (srv_innodb_directories != nullptr && *srv_innodb_directories != 0) {
fil_set_scan_dirs(Fil_path::remove_quotes(srv_innodb_directories));
}

/* Note whether the undo path is different (not the same or under)
from all other known directories. If so, this will allow us to keep
IBD files out of this unique undo location.*/
MySQL_undo_path_is_unique = !fil_path_is_known(MySQL_undo_path.path());

/* For the purpose of file discovery at startup, we need to scan
–innodb-undo-directory also if it is different from the locations above. */
if (MySQL_undo_path_is_unique) {
fil_set_scan_dir(Fil_path::remove_quotes(MySQL_undo_path));
}

3)buffer pool初始化-buf_pool_init

if (srv_buf_pool_size >= 1024 * 1024 * 1024) {
size = ((double)srv_buf_pool_size) / (1024 * 1024 * 1024);
unit = ‘G’;
} else {
size = ((double)srv_buf_pool_size) / (1024 * 1024);
unit = ‘M’;
}

double chunk_size;
char chunk_unit;

if (srv_buf_pool_chunk_unit >= 1024 * 1024 * 1024) {
chunk_size = srv_buf_pool_chunk_unit / 1024.0 / 1024 / 1024;
chunk_unit = ‘G’;
} else {
chunk_size = srv_buf_pool_chunk_unit / 1024.0 / 1024;
chunk_unit = ‘M’;
}

ib::info(ER_IB_MSG_1130, size, unit, srv_buf_pool_instances, chunk_size,
chunk_unit);

err = buf_pool_init(srv_buf_pool_size, srv_buf_pool_instances);

4)recv_sys, lock_sys, trx_sys,log_sys对象创建及初始化

fsp_init();
pars_init();
recv_sys_create();
recv_sys_init();
trx_sys_create();
lock_sys_create(srv_lock_table_size);

err = log_sys_init(create_new_db, flushed_lsn, new_files_lsn);

5)根据是否是第一次启动来判断是非需要创建新数据库。

bool create = (dict_init_mode == DICT_INIT_CREATE_FILES);

5.1 create_new_db == true时;
    1) 使用checkpoint lsn和当前的lsn来对log_sys对象进行设置并启动后台线程
err = log_start(*log_sys, new_files_lsn, new_files_lsn);

if (err != DB_SUCCESS) {
return srv_init_abort(err);
}

log_start_background_threads(*log_sys);

2)undo表空间初始化

err = srv_undo_tablespaces_init(true);

if (err != DB_SUCCESS) {
return (srv_init_abort(err));
}

mtr_start(&mtr);

bool ret = fsp_header_init(0, sum_of_new_sizes, &mtr);

mtr_commit(&mtr);

3)fsp_header页,trx_sys页,创建purge_sys对象,trx_sys对象初始化等

trx_sys_create_sys_pages();

trx_purge_sys_mem_create();

purge_queue = trx_sys_init_at_db_start();

/* The purge system needs to create the purge view and
therefore requires that the trx_sys is inited. */

trx_purge_sys_initialize(srv_threads.m_purge_workers_n, purge_queue);

4)dictionary 页创建,sid索引创建

err = dict_create();

if (err != DB_SUCCESS) {
return (srv_init_abort(err));
}

srv_create_sdi_indexes();

5)double write buffer创建

err = dblwr::v1::create();

5.2 create_new_db == false时;属于重新启动
   1)打开系统表空间
      fil_open_system_tablespace_files();

2)进行recovery操作,如果需要recovery则进行恢复操作

err = recv_recovery_from_checkpoint_start(*log_sys, flushed_lsn);

err = recv_apply_hashed_log_recs(*log_sys,
!recv_sys->is_cloned_db && !log_upgrade);

  3)打开undo表空间和purge_sys对象初始化等等
err = srv_undo_tablespaces_init(false);

if (err != DB_SUCCESS && srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN) {
return (srv_init_abort(err));
}

trx_purge_sys_mem_create();

/* The purge system needs to create the purge view and
therefore requires that the trx_sys is inited. */
purge_queue = trx_sys_init_at_db_start();

6)Undo 表空间创建和回滚段设置

/* Make sure there are enough rollback segments in each tablespace
and that each rollback segment has an associated memory object.
If any of these rollback segments contain undo logs, load them into
the purge queue */
if (!trx_rseg_adjust_rollback_segments(srv_rollback_segments)) {
return (srv_init_abort(DB_ERROR));
}

/* Any undo tablespaces under construction are now fully built
with all needed rsegs. Delete the trunc.log files and clear the
construction list. */
srv_undo_tablespaces_mark_construction_done();

7)其他:包括:锁等待线程,page clean 线程唤醒等等

srv_threads.m_lock_wait_timeout = os_thread_create(
srv_lock_timeout_thread_key, 0, lock_wait_timeout_thread);

srv_threads.m_lock_wait_timeout.start();

/* Create the thread which warns of long semaphore waits */
srv_threads.m_error_monitor = os_thread_create(srv_error_monitor_thread_key,
0, srv_error_monitor_thread);

srv_threads.m_error_monitor.start();

/* Create the thread which prints InnoDB monitor info */
srv_threads.m_monitor =
os_thread_create(srv_monitor_thread_key, 0, srv_monitor_thread);

srv_threads.m_monitor.start();
}

/* wake main loop of page cleaner up */
os_event_set(buf_flush_event);

欢迎关注公众号: DatabaseHacker

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

 

新的一年

回看去年,22年过的也不是很顺利,中间发生了很多不愉快的事情。想写些东西,感觉又是无从下笔。真的不知道写些什么。希望23年能够好点吧。