MySQL 存储引擎

2019-11-12 database mysql

众所周知,在 MySQL 中很多功能是通过插件实现的,包括了其中的存储引擎。在此简单介绍一下与 MySQL 存储引擎相关的内容,包括了提供的接口,实现方法等。

MySQL 插件 这篇文章中,已经讨论了与插件相关的内容,包括了编译、加载、使用方法等,同时也包括了存储引擎,详细使用方法可以参考这篇文章。

在此,仅介绍下 MySQL 中,存储引擎的实现。

简介

实际上,所为的存储引擎,是按照按照 MySQL 的接口定义,提供所需接口的实现而已;如插入一条记录时将调用 write_row(),通过索引检索时将调用 index_read()index_next() 等。

抽象后的接口极大地加快了在 MySQL 中加入其它存储引擎的步伐,该接口在 v3.22 升级到 v3.23 时引入,在快速集成 InnoDB 存储引擎时起了很大的帮助。

接下来看看 MySQL 的存储引擎是如何实现的。

数据结构

MySQL 与存储引擎之间的接口主要由 sql/handler.h 文件中的 class handlerstruct handlerton。其中两者的主要区别是:struct handlerton 定义了事务操作接口;class handler 定义了表、索引及记录操作接口。

实际上,最初版本只存在 handler ,从 5.0 版本开始,为了避免在初始化/事务提交/保存事务点/回滚操作时需要操作 one-table 实例,才引入了 handlerton 。

也就是说,对不需要支持事务的存储引擎只需要创建一个 handler 的派生类,并重载该引擎需要支持的方法;而对于需要支持事务的存储引擎,还需要实现 handlerton 结构中相应的接口。

class handler@sql/handler.h

这个类定义了对表操作的常见接口,其只继承了 Sql_alloc 类,而 Sql_alloc 没有任何成员变量,纯粹重载了 newdelete 操作。

所以 handler 类分配内存是可以从连接相关的内存池来分配;而删除时不需要做任何事情,内存释放只会在 mysys/my_alloc.c 中的 free_root() 调用发生,无需显性去释放,在语句执行之后清理。

class handler :public Sql_alloc
{
public:
    handlerton *ht;                  // 该handler的存储引擎
    uchar *ref;                      // 当前行的指针
    uint auto_inc_intervals_count;   // 自增值对应的内部变量
public:
    int ha_rnd_init(bool scan) __attribute__ ((warn_unused_result));
    int ha_rnd_end();
    int ha_rnd_init_with_error(bool scan) __attribute__ ((warn_unused_result));
    int ha_reset();
    int ha_external_lock(THD *thd, int lock_type);
    int ha_write_row(uchar * buf);
    int ha_update_row(const uchar * old_data, uchar * new_data);
    int ha_delete_row(const uchar * buf);
    void ha_release_auto_increment();

    // 创建表
    //   name: 创建表名;
    //   from: 表类型的结构,要创建表的定义,跟tablename.frm文件内容是匹配的
    //   info: 含客户端输入的CREATE TABLE语句的信息
    int ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info);

    // 打开一个表
    int ha_open(TABLE *table, const char *name, int mode, uint test_if_locked);

    // 关闭表
    int ha_close(void);

    // 当客户端调用LOCK TABLE时
    virtual int external_lock(THD *thd __attribute__((unused)),
                              int lock_type __attribute__((unused)));

    // 初始化全表扫描,后者会带有统计值
    int rnd_init(bool scan) __attribute__ ((warn_unused_result));
    int ha_rnd_init(bool scan) __attribute__ ((warn_unused_result));
    // 从表中读取下一行
    virtual int rnd_next(uchar *buf)=0;
    virtual int ha_rnd_next(uchar *buf);

    // 使用索引前调用该方法
    virtual int index_init(uint idx, bool sorted);
    // 使用索引后调用该方法
    virtual int index_end();
    // 读取索引第一条内容
    int ha_index_first(uchar * buf);
    // 读取索引下一条内容
    int ha_index_next(uchar * buf);
    // 读取索引前一条内容
    int ha_index_prev(uchar * buf);
    // 读取索引最后一条内容
    int ha_index_last(uchar * buf);
    // 给定一个key基于索引读取内容
    virtual int index_read(uchar * buf, const uchar * key, uint key_len,
                         enum ha_rkey_function find_flag);

    // 开始一个事务
    virtual int start_stmt(THD *thd, thr_lock_type lock_type);
};

对表接口的抽象类,提供了针对单个表的操作,如 open()write_row() 等,大约 150 种方法。每一个 table 描述符对应一个 handler 实例,如果同一个 table 被打开多次,那么这时候会出现多个 handler 实例。

struct handlerton@sql/handler.h

每个存储引擎只有一个该结构,提供了会影响整个存储引擎的接口,负责存储引擎初始化,事务相关操作等,如 commit()show_status() 等,大约有 30 多种。

struct handlerton {
    int  (*close_connection)(handlerton *hton, THD *thd);
    void (*kill_query)(handlerton *hton, THD *thd, enum thd_kill_levels level);



    // 提交一个事务
    int (*commit)(handlerton *hton, THD *thd, bool all);
    // 回滚一个事务
    int  (*rollback)(handlerton *hton, THD *thd, bool all);
};

在插件初始化的时候,会对 handlerton 对象进行初始化操作,而该对象中又包含了对 handler 的创建入口。

添加三方存储引擎

实际上,在 5.1 之后版本添加变得简单多,可以根据 “blackhole” 存储引擎的模式来改造;另外,还有 “example” 存储引擎可以参考,可以参考 MySQL 插件详解 中的介绍。

源码解析

在此简单介绍 MySQL 中,存储引擎的接口调用过程。

在此,以二级索引读取为例,其入口函数实际为 ha_index_next()@sql/handler.cc,而最终是通过 MYSQL_TABLE_IO_WAIT() 宏调用,如未开启 PSI 接口,则直接调用 index_next(buf) 函数。

int handler::ha_index_next(uchar * buf)
{
  int result;
  DBUG_ENTER("handler::ha_index_next");
  DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE ||
              m_lock_type != F_UNLCK);
  DBUG_ASSERT(inited == INDEX);
  DBUG_ASSERT(!pushed_idx_cond || buf == table->record[0]);

  // Set status for the need to update generated fields
  m_update_generated_read_fields= table->has_gcol();

  MYSQL_TABLE_IO_WAIT(PSI_TABLE_FETCH_ROW, active_index, result,
    { result= index_next(buf); })
  if (!result && m_update_generated_read_fields)
  {
    result= update_generated_read_fields(buf, table, active_index);
    m_update_generated_read_fields= false;
  }
  DBUG_RETURN(result);
}

对应不同的存储引擎,实际是通过类似 int index_next(uchar * buf) 函数读取的,如下是 handler 的定义,不同的存储引擎会定义不同接口函数。

class handler :public Sql_alloc
{
  /// @returns @see index_read_map().
  virtual int index_next(uchar * buf)
   { return  HA_ERR_WRONG_COMMAND; }
}

那么,对于不同的接口,会有不同的调用,视情况而定。

InnoDB

实际上,在 handler/ha_innodb.cc 文件中,除了定义 innodb 存储引擎之外,还包括了一些其它插件,如 trx、locks 等,可以通过 SHOW PLUGINS 查看。如下是源码中的部分内容:

mysql_declare_plugin(innobase)
{
  MYSQL_STORAGE_ENGINE_PLUGIN,
  &innobase_storage_engine,
  innobase_hton_name,
  plugin_author,
  "Supports transactions, row-level locking, and foreign keys",
  PLUGIN_LICENSE_GPL,
  innobase_init, /* Plugin Init */
  NULL, /* Plugin Deinit */
  INNODB_VERSION_SHORT,
  innodb_status_variables_export,/* status variables             */
  innobase_system_variables, /* system variables */
  NULL, /* reserved */
  0,    /* flags */
},
i_s_innodb_trx,
i_s_innodb_locks,
i_s_innodb_lock_waits,
i_s_innodb_cmp,
i_s_innodb_cmp_reset,
... ...,
mysql_declare_plugin_end;