众所周知,在 MySQL 中很多功能是通过插件实现的,包括了其中的存储引擎。在此简单介绍一下与 MySQL 存储引擎相关的内容,包括了提供的接口,实现方法等。
在 MySQL 插件 这篇文章中,已经讨论了与插件相关的内容,包括了编译、加载、使用方法等,同时也包括了存储引擎,详细使用方法可以参考这篇文章。
在此,仅介绍下 MySQL 中,存储引擎的实现。
简介
实际上,所为的存储引擎,是按照按照 MySQL 的接口定义,提供所需接口的实现而已;如插入一条记录时将调用 write_row()
,通过索引检索时将调用 index_read()
和 index_next()
等。
抽象后的接口极大地加快了在 MySQL 中加入其它存储引擎的步伐,该接口在 v3.22
升级到 v3.23
时引入,在快速集成 InnoDB 存储引擎时起了很大的帮助。
接下来看看 MySQL 的存储引擎是如何实现的。
数据结构
MySQL 与存储引擎之间的接口主要由 sql/handler.h
文件中的 class handler
和 struct handlerton
。其中两者的主要区别是:struct handlerton
定义了事务操作接口;class handler
定义了表、索引及记录操作接口。
实际上,最初版本只存在 handler ,从 5.0 版本开始,为了避免在初始化/事务提交/保存事务点/回滚操作时需要操作 one-table 实例,才引入了 handlerton 。
也就是说,对不需要支持事务的存储引擎只需要创建一个 handler 的派生类,并重载该引擎需要支持的方法;而对于需要支持事务的存储引擎,还需要实现 handlerton 结构中相应的接口。
class handler@sql/handler.h
这个类定义了对表操作的常见接口,其只继承了 Sql_alloc
类,而 Sql_alloc
没有任何成员变量,纯粹重载了 new
和 delete
操作。
所以 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;