MySQL插件接口的调用方式

author:sufei

版本:8.0.16


一、简介

 首先简单说明一下插件的实现原理

在程序的合适位置(挂钩处)安插相应的函数指针,相应的结构类似:if (fun_ptr != null) fun_ptr();

  • 在没有插件安装时,这些函数指针为空,程序运行到挂钩处没有任何操作,不会进入插件相关代码;
  • 在安装了插件时,安装过程会为相应的函数指针赋值,fun_ptr不在为null,从而当程序运行到挂钩处,则进入相应的插件处理逻辑。

 虽然插件的基本实现原理大致如上述所说,但对于mysql的插件实现来说,存在多种调用方式。下面具体讲解各种调用方式的初始化以及调用过程,以便在阅读mysql源码时更好的理解插件调用过程。

二、插件访问方式

2.1 观察者方式

 这是服务层调用插件是非常常见的方式。MySQL服务器将观察者的挂钩按照用途分为五类,并且定义了五类观察者的全局变量,用于惯例不同种类的挂钩。分别为:

//观察者代表类
Trans_delegate *transaction_delegate;   
Binlog_storage_delegate *binlog_storage_delegate;
Server_state_delegate *server_state_delegate;

Binlog_transmit_delegate *binlog_transmit_delegate;
Binlog_relay_IO_delegate *binlog_relay_io_delegate;

 下面对这5类观察者进行简单说明:

  • Trans_delegate <font color=blue>事务观察者</font>:其主要用于事务挂钩,包含:

before_dmlbefore_commitbefore_rollbackafter_commitafter_rollbacktrans_begin

顾名思义,这些就是事务观察者的挂钩位置。如果插件想在这些位置进入插件处理逻辑,就需要向Trans_delegate注册。比如MGR就注册了事务观察者,其提交前(before_commit位置处)调用注册时的相应函数进入冲突检测阶段。

  • Binlog_storage_delegate <font color=blue>binlog存储观察者</font>:其主要用于binlog同步位置挂钩,包含:

after_flushafter_sync

即在相应的binlog同步点处的挂钩。如果插件向Binlog_storage_delegate进行了注册,则会在相应位置点调用相应的注册函数。比如半同步插件,就是注册了binlog存储观察者,从而实现不同的ack等待点。

  • Server_state_delegate <font color=blue>服务器状态观察者</font>:主要是在服务器状态启停处设置的挂钩,包含:before_handle_connectionbefore_recoveryafter_engine_recoveryafter_recoverybefore_server_shutdownafter_server_shutdown
  • Binlog_transmit_delegate <font color=blue>binlog传输观察者</font>:主要在binlog传输各节点设置的挂钩,包含:

transmit_starttransmit_stopreserve_headerbefore_send_eventafter_send_eventafter_reset_master

在发送binlog各节点处的挂钩,可以实现对binlog传输控制,如在半同步插件中,通过注册binlog传输观察者,当从库开启复制时,主库start dump线程,会调用transmit_start函数,进而半同步插件通过该调用添加半同步从库。

  • Binlog_relay_IO_delegate <font color=blue>relay IO 观察者</font>:主要在是在从库复制线程上的挂钩,包含:

thread_startthread_stopapplier_startapplier_stopbefore_request_transmitafter_read_eventafter_queue_eventafter_reset_slaveapplier_log_event

在从库回放个各位置设置的挂钩,从而使得插件得以控制回放过程。

 上面介绍了各类观察者,既然是观察者,就应该分为观察者的注册过程,以及调用过程。

2.1.1 注册

 要明白注册的过程,首先需要知道上面5类观察者都是Delegate的子类,而Delegate类主要维护着observer_info_list列表(也就是该类被成员锁lock保护注册列表)。列表的节点为Observer_info类型,其中就保存着插件相关信息。

class Observer_info {
 public:
  void *observer;
  st_plugin_int *plugin_int;
  plugin_ref plugin;

  Observer_info(void *ob, st_plugin_int *p);
};
Observer_info::Observer_info(void *ob, st_plugin_int *p)
    : observer(ob), plugin_int(p) {
  plugin = plugin_int_to_ref(plugin_int);
}

class Delegate {
 public:
  typedef List<Observer_info> Observer_info_list;
  typedef List_iterator<Observer_info> Observer_info_iterator;
  
  //向注册列表中添加相应注册
  int add_observer(void *observer, st_plugin_int *plugin) {
    int ret = false;
    if (!inited) return true;
    write_lock();
    Observer_info_iterator iter(observer_info_list);
    Observer_info *info = iter++;
    while (info && info->observer != observer) info = iter++;
    if (!info) {
      info = new Observer_info(observer, plugin); //构建新的Observer_info
      if (!info || observer_info_list.push_back(info, &memroot)) ret = true;
    } else
      ret = true;
    unlock();
    return ret;
  }
  //向注册列表中注销相应注册
  int remove_observer(void *observer) {
    int ret = false;
    if (!inited) return true;
    write_lock();
    Observer_info_iterator iter(observer_info_list);
    Observer_info *info = iter++;
    while (info && info->observer != observer) info = iter++;
    if (info) {
      iter.remove();
      delete info;
    } else
      ret = true;
    unlock();
    return ret;
  }

  inline Observer_info_iterator observer_info_iter() {
    return Observer_info_iterator(observer_info_list);
  }
  //注册列表是否为空,空着说明没有相关注册,即在该类挂钩处直接返回
  inline bool is_empty() {
    DBUG_PRINT("debug", ("is_empty: %d", observer_info_list.is_empty()));
    return observer_info_list.is_empty();
  }

  inline int read_lock() {
    if (!inited) return true;
    return mysql_rwlock_rdlock(&lock);
  }

  inline int write_lock() {
    if (!inited) return true;
    return mysql_rwlock_wrlock(&lock);
  }

  inline int unlock() {
    if (!inited) return true;
    return mysql_rwlock_unlock(&lock);
  }

  inline bool is_inited() { return inited; }

  explicit Delegate(
#ifdef HAVE_PSI_RWLOCK_INTERFACE
      PSI_rwlock_key key
#endif
  );

  ~Delegate() {
    inited = false;
    mysql_rwlock_destroy(&lock);
    free_root(&memroot, MYF(0));
  }

 private:
  Observer_info_list observer_info_list;  //维护的注册列表
  mysql_rwlock_t lock;
  MEM_ROOT memroot;
  bool inited;
};

 <font color=red>注册的过程就是向成员observer_info_list列表中添加Observer_info的过程</font>。下面我们通过半同步插件的binlog存储观察者(即Binlog_storage_delegate)注册过程,来详解分析注册机制。其他观察者的注册过程类似。

  • Binlog_storage_delegate观察者的注册过程

 5个全局的观察者变量,每类观察者都有一个相应的注册函数以及相应的注册函数结构体,对于binlog存储观察者,其注册函数如下:

/*
binlog存储观察注册函数
参数: observer :binlog存储观察者注册函数结构体
      p        :插件结构体指针
*/
int register_binlog_storage_observer(Binlog_storage_observer *observer,
                                     void *p) {
  DBUG_ENTER("register_binlog_storage_observer");
  int result =
      binlog_storage_delegate->add_observer(observer, (st_plugin_int *)p);
  DBUG_RETURN(result);
}
// binlog存储观察者注册函数结构体,最终会被关联在Observer_info->observer成员中
typedef struct Binlog_storage_observer {
  uint32 len;

  after_flush_t after_flush;
  after_sync_t after_sync;
} Binlog_storage_observer;

 相对来说,注册函数非常简单,仅仅调用基类的add_observer函数即可。而对于半同步插件在插件初始化函数semi_sync_master_plugin_init中调用相关注册函数。

  if (register_binlog_storage_observer(&storage_observer, p)) { //注册binlog存储观察
    deinit_logging_service_for_plugin(&reg_srv, &log_bi, &log_bs);
    return 1;
  }

 而相应的注册函数结构体storage_observer,定义如下

Binlog_storage_observer storage_observer = {
    sizeof(Binlog_storage_observer),  // len

    repl_semi_report_binlog_update,  // report_update
    repl_semi_report_binlog_sync,    // after_sync
};

 通过上述分析,注册的过程大致过程如下:

  • 相关注册函数构建成注册结构体,如上述将repl_semi_report_binlog_update和repl_semi_report_binlog_sync函数作为成员构建Binlog_storage_observer结构体;
  • 通过相应的注册函数,将相应observer结构体添加到相应观察者注册链表中。

 通过上述两步,将相应的注册函数被放入观察者的注册链表中,在调用过程通过遍历注册链表,调用相应函数,完成注册函数的调用。下面会具体分析。

2.1.2 调用过程

 注册了相应观察者后,通常是通过定义的参数宏来实现调用的。同样以binlog存储观察者注册结构体中的注册函数after_sync为例,来说明完整的调用过程。

  • after_sync挂钩安放位置

 在组提交sync阶段之后,引擎层commit阶段前,会调用如下函数:

sync_error = call_after_sync_hook(commit_queue);

 而该函数的内部有调用了如下宏

RUN_HOOK(binlog_storage, after_sync, (queue_head, log_file, pos))

 宏的扩展为:

#define RUN_HOOK(group, hook, args) \
  (group##_delegate->is_empty() ? 0 : group##_delegate->hook args)

 实际调用为:

(binlog_storage_delegate->is_empty()? 0 : binlog_storage_delegate->after_sync(queue_head, log_file, pos))

 从上面可以看到:首先判断binlog存储观察者注册列表是否为空,为空则直接返回;不为空则调用

binlog_storage_delegate->after_sync成员函数。下面我们具体看一下其实现。

  • binlog存储观察者after_sync成员函数实现

 首先看一下Binlog_storage_delegate类的实现.

class Binlog_storage_delegate : public Delegate {
 public:
  Binlog_storage_delegate()
      : Delegate(
#ifdef HAVE_PSI_RWLOCK_INTERFACE
            key_rwlock_Binlog_storage_delegate_lock
#endif
        ) {
  }
  //重定义Observer为Binlog_storage_observer注册结构体类,以便后续成员函数查找相应的注册函数
  typedef Binlog_storage_observer Observer; 
  /*
  对于该类管理的所有hook挂钩,都以一个相应的成员函数。如after_sync挂钩处调用after_sync函数
  */
  int after_flush(THD *thd, const char *log_file, my_off_t log_pos);
  int after_sync(THD *thd, const char *log_file, my_off_t log_pos);
};

 针对after_sync函数,其实现如下:

int Binlog_storage_delegate::after_sync(THD *thd, const char *log_file,
                                        my_off_t log_pos) {
  DBUG_ENTER("Binlog_storage_delegate::after_sync");
  DBUG_PRINT("enter",
             ("log_file: %s, log_pos: %llu", log_file, (ulonglong)log_pos));
  Binlog_storage_param param;
  param.server_id = thd->server_id;

  DBUG_ASSERT(log_pos != 0);
  int ret = 0;
  FOREACH_OBSERVER(ret, after_sync, (&param, log_file, log_pos)); //调用注册列表中相应函数

  DEBUG_SYNC(thd, "after_call_after_sync_observer");
  DBUG_RETURN(ret);
}

 从上面可以看到:after_sync成员函数实际调用FOREACH_OBSERVER参数宏

#define FOREACH_OBSERVER(r, f, args)                                   \
  Prealloced_array<plugin_ref, 8> plugins(PSI_NOT_INSTRUMENTED);       \
  read_lock();                                                         \
  Observer_info_iterator iter = observer_info_iter();                  \
  Observer_info *info = iter++;                                        \
  for (; info; info = iter++) {          //遍历注册列表                  \
    plugin_ref plugin = my_plugin_lock(0, &info->plugin);              \
    if (!plugin) {                                                     \
      /* plugin is not intialized or deleted, this is not an error */  \
      r = 0;                                                           \
      break;                                                           \
    }                                                                  \
    plugins.push_back(plugin);                                         \
    //如果注册结构体observer中相应注册函数不为空,则调用相应注册函数指针         \
    if (((Observer *)info->observer)->f &&                             \
        ((Observer *)info->observer)->f args) {                        \
      r = 1;                                                           \
      LogEvent()                                                       \
          .prio(ERROR_LEVEL)                                           \
          .errcode(ER_RPL_PLUGIN_FUNCTION_FAILED)                      \
          .subsys(LOG_SUBSYSTEM_TAG)                                   \
          .function(#f)                                                \
          .message("Run function '" #f "' in plugin '%s' failed",      \
                   info->plugin_int->name.str);                        \
      break;                                                           \
    }                                                                  \
  }                                                                    \
  unlock();                                                            \
  /*                                                                   \
     Unlock plugins should be done after we released the Delegate lock \
     to avoid possible deadlock when this is the last user of the      \
     plugin, and when we unlock the plugin, it will try to             \
     deinitialize the plugin, which will try to lock the Delegate in   \
     order to remove the observers.                                    \
  */                                                                   \
  if (!plugins.empty()) plugin_unlock_list(0, &plugins[0], plugins.size());

 下面对上述宏做一个简单说明:

  • 遍历binlog存储观察类的Observer_info列表;
  • 取出Observer_info的observer指针,将其转换为Binlog_storage_observer类型(宏中是转换为Observer类型,但在该类中存在typedef Binlog_storage_observer Observer,故最终转换为binlog存储类的注册结构体Binlog_storage_observer类型);
  • 查找注册结构体中的相应注册函数after_sync是否为空,不为空则调用,从而进入插件逻辑。

 通过上面,我们也看到了观察点的调用过程。下面是是对插件在观察者方式下调用总结。

2.1.3 总结

观察者模式

注册:

  • 首先构建相应的注册结构体,对于binlog 存储观察类,也就是Binlog_storage_observer结构体,其中包含了相应注册函数指针;
  • 构建Observer_info类型,其中observer指针指向注册结构体,如Binlog_storage_observer;
  • 调用各类观察者相应的注册函数,如register_binlog_storage_observer,将上述Observer_info注册到Binlog_storage_delegate的注册列表中

调用

  • mysql在合适的调用位置,都设置相应的RUN_HOOK宏;

如RUN_HOOK(binlog_storage, after_sync, (queue_head, log_file, pos))则是在组提交sync之后设置的挂钩宏

  • 通过参数宏替换,调用观察者的相应成员函数,最终会遍历注册链表中所有相应的设置函数。

对于上述例子,调用binlog_storage_delegate成员函数after_sync,最终也就是遍历binlog_storage_delegate注册链表中observer结构体内的after_sync函数,参数由RUN_HOOK中的(queue_head, log_file, pos)指定。

2.2 handlerton方式

 该方式主要操作存储引擎的主要方式。

在存储引擎中表的操作以及事务相关的操作都是通过这种方式来访问插件引擎层的。如create创建表,commit提交事务等操作。

 下面我们来具体讲解此种方式访问的过程。

2.2.1 函数挂载

 对于插件存储引擎,mysql都会有一个handlerton类型的结构体与之对应,通常命名为xxx_hton。对于innodb存储引擎,即为innobase_hton。handlerton类型结构体的成员包含:

  • 一些类型状态说明变量;
SHOW_COMP_OPTION state;       //标记引擎状态
enum legacy_db_type db_type;  //存储引擎类型
uint slot;
  • 数据表相应操作的函数指针。
//以下都是表相应操作的函数指针成员
close_connection_t close_connection;
kill_connection_t kill_connection;
pre_dd_shutdown_t pre_dd_shutdown;
savepoint_set_t savepoint_set;
savepoint_rollback_t savepoint_rollback;
savepoint_rollback_can_release_mdl_t savepoint_rollback_can_release_mdl;
savepoint_release_t savepoint_release;
commit_t commit;
rollback_t rollback;
prepare_t prepare;
……

 上面介绍了handlerton结构体的构成,下面我们来看一下对于一个特定引擎的handlerton(如innobase_hton )是如何挂载到mysql服务器上,以便后续服务层当需要操作该引擎的表时,通过该结构体内的函数指针,调用内部相应的函数真正引擎层操作物理表。

 要理解引擎的handlerton的挂载过程,实际就是理解存储引擎install过程,以及mysql插件管理过程。

1. 插件管理

 mysql服务器通过如下全局变量来管理所有安装的插件。

static collation_unordered_map<std::string, st_plugin_int *>
    *plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM] = {nullptr};

该变量是一个map指针数组。对于每一类插件,都存放在一个map结构体中,所有map结构体指针都存放在plugin_hash数组中。

一个安装的插件,我们可以通过插件类型以及插件名找到插件handle句柄(即st_plugin_int结构体),过程都在函数plugin_find_internal

/*
从安装的插件中,获取特定插件的操作handle句柄
参数:
    name : 插件名
    type : 插件类型
*/
static st_plugin_int *plugin_find_internal(const LEX_CSTRING &name, int type) {
  uint i;
  DBUG_ENTER("plugin_find_internal");
  if (!initialized) DBUG_RETURN(NULL);

  mysql_mutex_assert_owner(&LOCK_plugin);

  if (type == MYSQL_ANY_PLUGIN) {  //没有指定插件类型,则遍历所有插件类型,查找名为name的插件
    for (i = 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) {
      const auto it = plugin_hash[i]->find(to_string(name));
      if (it != plugin_hash[i]->end()) DBUG_RETURN(it->second);
    }
  } else //如果指定了插件类型,直接到相应map中查找即可
    DBUG_RETURN(find_or_nullptr(*plugin_hash[type], to_string(name)));
  DBUG_RETURN(NULL);
}

 明白了mysql内部是如何管理插件,以及如何查找相应插件。下面我们需要讲解插件的安装以及初始化(handlerton挂载)。

2.插件的安装以及初始化(handlerton挂载)

 在讲解插件安装过程之前,我们先看看插件相关结构体以及之间的关系。

插件相关结构体

 在执行install指令安装插件时,内部会调用mysql_install_plugin函数,下面我们来具体看一下该函数的执行过程。

  • 打开mysql.plugins表,以便后续进行写表
  /* need to open before acquiring LOCK_plugin or it will deadlock */
  if (!(table = open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
    DBUG_RETURN(true);
  • 构建st_plugin_int结构体(参考plugin_add函数),即插件句柄,并插入到管理变量plugin_hash中,其中包含以下几步
  1. st_plugin_dl动态库结构体体构建,主要是调用plugin_dl_add函数,加载动态库,版本检测,最后将动态库中插件描述结构体指针赋值给st_plugin_dl->plugins
  2. 从动态库结构体中的plugins中,寻找指定插件名的插件,找到了,则加载该插件描述结构到st_plugin_int->plugin,并将state状态设置为PLUGIN_IS_UNINITIALIZED
  3. 将st_plugin_int插入到管理变量plugin_hash中
  /* 循环查找,插件名一致的插件描述结构体 */
  for (plugin = tmp.plugin_dl->plugins; plugin->info; plugin++) {
    size_t name_len = strlen(plugin->name);
    //版本检测以及插件名检测是否都满足
    if (plugin->type >= 0 && plugin->type < MYSQL_MAX_PLUGIN_TYPE_NUM &&
        !my_strnncoll(system_charset_info,
                      pointer_cast<const uchar *>(name->str), name->length,
                      pointer_cast<const uchar *>(plugin->name), name_len)) {
      ……
      }
      // 找到相应插件描述结构体,进行插件句柄赋值
      tmp.plugin = plugin;
      tmp.name.str = (char *)plugin->name;
      tmp.name.length = name_len;
      tmp.ref_count = 0;
      tmp.state = PLUGIN_IS_UNINITIALIZED;  //标记插件状态
      tmp.load_option = PLUGIN_ON;    //标记加载状态
      if (test_plugin_options(tmp_root, &tmp, argc, argv))
        tmp.state = PLUGIN_IS_DISABLED;
      
      if ((tmp_plugin_ptr = plugin_insert_or_reuse(&tmp))) {
        plugin_array_version++; 
        //将插件句柄插入到插件管理变量plugin_hash中
        if (plugin_hash[plugin->type]
                ->emplace(to_string(tmp_plugin_ptr->name), tmp_plugin_ptr)
                .second) {
          init_alloc_root(key_memory_plugin_int_mem_root,
                          &tmp_plugin_ptr->mem_root, 4096, 4096);
          DBUG_RETURN(false);
        }
        tmp_plugin_ptr->state = PLUGIN_IS_FREED;
      }
      mysql_del_sys_var_chain(tmp.system_vars);
      restore_pluginvar_names(tmp.system_vars);
      plugin_dl_del(dl);
      mysql_rwlock_unlock(&LOCK_system_variables_hash);
      mysql_mutex_unlock(&LOCK_plugin);
      DBUG_RETURN(true);
    }
  }
  • 检测相应结构体是否添加到plugin_hash中,并将相关插件信息写入mysql.plugins表
// 找到相应插件句柄
if (!(tmp = plugin_find_internal(name_cstr, MYSQL_ANY_PLUGIN))) {
  mysql_mutex_unlock(&LOCK_plugin);
  goto err;
}

Disable_binlog_guard binlog_guard(thd); //写入plugins表时关闭binlog,避免主从同步
table->use_all_columns();
restore_record(table, s->default_values);
table->field[0]->store(name->str, name->length, system_charset_info);
table->field[1]->store(dl->str, dl->length, files_charset_info);
error = table->file->ha_write_row(table->record[0]);  //将插件信息写入表中
  • 调用plugin_initialize函数,对插件进行初始化,初始化过程有三步
  1. 调用插件初始化函数
  2. 添加插件状态变量
  3. 将插件系统变量添加到服务器系统变量管理结构
//插件初始化函数,会调用插件声明时初始化函数指针
static int plugin_initialize(st_plugin_int *plugin) {
  ....
  /*
  根据插件类型不同,初始化过程也不同,其过程被plugin_type_initialize数组指定
  */
  if (plugin_type_initialize[plugin->plugin->type]) {
  //如果plugin_type_initialize数组相应插件位置不为空,则调用数组指定函数进行插件初始化,参数为plugin
    if ((*plugin_type_initialize[plugin->plugin->type])(plugin)) {
      LogErr(ERROR_LEVEL, ER_PLUGIN_REGISTRATION_FAILED, plugin->name.str,
             plugin_type_names[plugin->plugin->type].str);
      goto err;
    }
    ....
  } else if (plugin->plugin->init) {//为空,直接调用声明的插件初始化函数,参数为st_plugin_int类型
    ....
    if (plugin->plugin->init(plugin)) {
      LogErr(ERROR_LEVEL, ER_PLUGIN_INIT_FAILED, plugin->name.str);
      goto err;
    }
  }
  state = PLUGIN_IS_READY;  // plugin->init() succeeded
  //添加插件状态变量
  if (plugin->plugin->status_vars) {
    if (add_status_vars(plugin->plugin->status_vars)) goto err;
  }
  //添加插件系统变量
  if (plugin->system_vars) {
    sys_var_pluginvar *var = plugin->system_vars->cast_pluginvar();
    for (;;) {
      var->plugin = plugin;
      if (!var->next) break;
      var = var->next->cast_pluginvar();
    }
  }

  ret = 0;

err:
  mysql_mutex_lock(&LOCK_plugin);
  plugin->state = state;
  DBUG_RETURN(ret);
}

 通过上面可知,不同插件初始化方式不一样,与plugin_type_initialize数组有关,对此我们看一下该数组

plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
    0,
    ha_initialize_handlerton,  //存储引擎插件类型
    0,
    0,
    initialize_schema_table,   //I_S插件类型
    initialize_audit_plugin,   //audit审计插件类型
    0,
    0,
    0};

 进入ha_initialize_handlerton存储引擎插件的初始化函数,我们发现调用插件描述结构体中初始化函数的参数不在为插件句柄st_plugin_int,而是handlerton类型,如下:

//创建handlerton结构体指针,并分配空间
hton = (handlerton *)my_malloc(key_memory_handlerton, sizeof(handlerton),
                                 MYF(MY_WME | MY_ZEROFILL));

if (hton == NULL) {
  LogErr(ERROR_LEVEL, ER_HANDLERTON_OOM, plugin->name.str);
  goto err_no_hton_memory;
}

plugin->data = hton;  //将handlerton绑定到该插件句柄的data成员
/*
调用插件描述符结构体中的初始化函数,传参为handlerton指针,这样在初始化函数中
即可完成对handlerton中函数指针进行赋值。
*/
if (plugin->plugin->init && plugin->plugin->init(hton)) {
  LogErr(ERROR_LEVEL, ER_PLUGIN_INIT_FAILED, plugin->name.str);
  goto err;
}

 如innodb插件初始化函数中handlerton结构体的赋值过程。

static int innodb_init(void *p) {
  DBUG_ENTER("innodb_init");

  handlerton *innobase_hton = (handlerton *)p;
  innodb_hton_ptr = innobase_hton;

  innobase_hton->state = SHOW_OPTION_YES;
  innobase_hton->db_type = DB_TYPE_INNODB;
  innobase_hton->savepoint_offset = sizeof(trx_named_savept_t);
  innobase_hton->close_connection = innobase_close_connection;
  ...
}

 通过上述分析,已经明白了插件初始化过程以及引擎插件handlerton结构体绑定过程。下面的调用过程就相对简单一些。

2.2.2 调用过程

 调用过程相对简单,handlerton的调用方式从<font color=blue>plugin_foreach</font>参数宏开始,这里以innodb flush redolog调用为例,进行调用过程说明。

  • 存储引擎flush日志调用函数为ha_flush_logs,其核心为
/*
plugin_foreach宏操作:
1、找出指定类型的所有插件(这里为MYSQL_STORAGE_ENGINE_PLUGIN),找到其插件句柄
2、分别调用指定函数(这里为flush_handlerton),函数参数为宏的第一个参数(NULL,通常为thd),和最后一个参数(static_cast<void *>(&binlog_group_flush))
*/
if (plugin_foreach(NULL, flush_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN,
                     static_cast<void *>(&binlog_group_flush))) {
    return true;
}
  • 调用指定函数flush_handlerton
static bool flush_handlerton(THD *, plugin_ref plugin, void *arg) {
  //获得对于插件的handlerton结构体
  handlerton *hton = plugin_data<handlerton *>(plugin);
  //调用引擎的flush_logs函数
  if (hton->state == SHOW_OPTION_YES && hton->flush_logs &&
      hton->flush_logs(hton, *(static_cast<bool *>(arg))))
    return true;
  return false;
}
  • 调用引擎层innobase_flush_logs函数
//由于在innodb初始化过程,handlerton的初始化中flush_logs赋值如下,故最终调用引擎层innobase_flush_logs函数
innobase_hton->flush_logs = innobase_flush_logs;

2.3 线程私有数据访问方式

 对于每个连接线程在引擎层都有私有数据,比如

对于binlog存储引擎,有事务的binlog缓存;

对于innodb插件,有事务状态相关信息(如read_view一致性视图信息),本线程的用户临时表等信息。

 我们知道每个线程都有一个thd结构,保存着该线程的私有信息,同样,对于引擎层私有数据,也保存在该结构体中,通过ha_data成员。

//ha_data定义
Prealloced_array<Ha_data, PREALLOC_NUM_HA> ha_data;
//ha_data操作函数
Ha_data *get_ha_data(int slot) { return &ha_data[slot]; }
//从thd结构获得对应插件的私有数据
void **thd_ha_data(const MYSQL_THD thd, const struct handlerton *hton) {
  return &(const_cast<THD *>(thd))->get_ha_data(hton->slot)->ha_ptr;
}

 每类引擎插件都对应一个Ha_data结构体,这样可以每个线程保存引擎层的私有数据。

struct Ha_data {
  void *ha_ptr;  //引擎层私有数据结构体指针,生命周期为整个会话连接
  void *ha_ptr_backup;  //用于XA transaction,保存原来引擎数据
  /**
    0: Life time: one statement within a transaction. If @@autocommit is
    on, also represents the entire transaction.
    @sa trans_register_ha()

    1: Life time: one transaction within a connection.
    If the storage engine does not participate in a transaction,
    this should not be used.
    @sa trans_register_ha()
  */
  Ha_trx_info ha_info[2];

  /**
    NULL: engine is not bound to this thread
    non-NULL: engine is bound to this thread, engine shutdown forbidden
  */
  plugin_ref lock;

  Ha_data() : ha_ptr(NULL), ha_ptr_backup(NULL), lock(NULL) {}
};

下面我们以innodb引擎的线程私有数据访问方式来说明。

2.3.1 私有数据的设置和访问

 对于innodb引擎层私有数据保存在innodb_session_t结构体中,而该结构体的创建和绑定到thd结构体函数为thd_to_innodb_session

//获得thd结构innodb私有数据
innodb_session_t *&thd_to_innodb_session(THD *thd) {
  //首先innodb私有数据结构体指针,注意这里是引用
  innodb_session_t *&innodb_session =
      *(innodb_session_t **)thd_ha_data(thd, innodb_hton_ptr);
  //如果不为null,即返回私有数据指针
  if (innodb_session != NULL) {
    return (innodb_session);
  }
  /*
  如果为null,说明还没有构建私有数据,
  则新创建一个innodb私有数据结构体,并赋值给innodb_session,
  这样就为thd即绑定了一个innodb私有数据结构体,
  并返回
  */
  innodb_session = UT_NEW_NOKEY(innodb_session_t());
  return (innodb_session);
}

 所以innodb引擎私有数据可以通过上述函数进行创建和访问,这样在引擎层即可通过绑定到thd结构的事务信息明白各会话事务状态。

 而该私有数据结构的释放通常是在handlerton->close_connection指定函数中,对于innodb则是innobase_close_connection函数中。

UT_DELETE(thd_to_innodb_session(thd));

thd_to_innodb_session(thd) = NULL;

 从这一点可以看出,私有数据结构体的生命周期为整个连接过程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,097评论 1 32
  • 水平有限整理自己的学习笔记,如果有误请谅解。 最近老是看到有朋友报错如下:ERROR 1197 (HY000): ...
    重庆八怪阅读 2,904评论 0 6
  • 1.A simple master-to-slave replication is currently being...
    Kevin关大大阅读 5,966评论 0 3
  • 我有多久没有牵过您的手? 曾记何时,您辛苦的耕作于田地之间? 无怨无悔为我们的家默默奉献,您给了我们坚实的臂膀和爱...
    小小_默阅读 281评论 0 1
  • 1.事件:晚上和玉玲去淄博,先生主动开车送我俩。 2.感受:开心,幸福,温暖,骄傲,感动 3.想法:这人一会是魔鬼...
    我是爱和自由阅读 110评论 0 0