第七章 存储引擎接口
# 第七章 存储引擎接口
MySQL提供了一个抽象层,允许不同的存储引擎使用相同的应用程序编程接口(API)来访问它们的表。过去,这个接口被称为表处理器(table handler)。最近,引入了“存储引擎”(storage engine)这一术语。在当前的术语体系中,存储引擎指的是实际存储和检索数据的代码,而表处理器则是指存储引擎与MySQL优化器之间的接口。
这个抽象接口极大地简化了向MySQL添加新存储引擎的任务。它是在从3.22版本到3.23版本的过渡期间创建的,在InnoDB存储引擎的快速集成过程中发挥了重要作用。InnoDB存储引擎带来了强大的事务处理能力、多版本控制和行级锁。该接口可用于集成自定义存储引擎,这使你能够快速为几乎任何具备读写记录能力的系统开发一个SQL接口。
这个接口是通过一个名为handler
的抽象类实现的,它提供了诸如打开和关闭表、顺序扫描记录、根据键值检索记录、存储记录和删除记录等基本操作的方法。每个存储引擎都会实现handler
的一个子类,通过实现接口方法,将处理器操作转换为特定存储引擎的底层存储/检索API调用。从5.0版本开始,添加了handlerton
结构体,以便存储引擎能够为那些不一定涉及单表实例的操作(如初始化、事务提交、保存点和回滚)提供自己的挂钩函数。
在本章中,我们将研究handler
类和handlerton
结构体,然后提供一个简单的存储引擎作为示例供你学习,该存储引擎用于读取以逗号分隔的文件。
# handler类
handler
类在sql/handler.h
中定义,在sql/handler.cc
中实现。请注意,它是Sql_alloc
(在sql/sql_list.h
中定义)的子类。Sql_alloc
是一个没有成员的类,它仅仅重写了new
和delete
操作符,使得new
从与连接相关联的内存池中分配内存,而delete
不执行任何操作。实际上,它也无需执行操作,因为在执行完一条语句后的清理阶段,通过调用mysys/my_alloc.c
中的free_root()
,内存池中的所有内存会一次性被释放。
每个表描述符都会创建一个handler
实例。需要注意的是,同一个表可能有多个表描述符,因此在同一服务器中可能有同样数量的handler
实例。5.0版本中的新索引合并连接方法带来了一个有趣的变化。过去,同一个表的多个handler
实例仅仅是因为表缓存中有多个表描述符副本,所以每个描述符对应一个handler
实例。现在,随着索引合并的引入,在优化过程中可能会创建额外的handler
实例。
handler
的数据成员在表7 - 1中进行了说明,handler
的方法在表7 - 2中进行了说明。
表7 - 1:handler数据成员
成员定义 | 成员描述 |
---|---|
struct st_table *table | 与给定handler 实例相关联的表描述符。 |
byte *ref | 存储当前记录引用的值。记录引用是给定表的一个内部唯一记录标识符。对于这个字段,MyISAM使用记录在数据文件中的偏移量;InnoDB使用经过特殊格式化的主键值;MEMORY使用指向记录起始位置的指针。该值的长度存储在ref_length 成员中。 |
byte *dupp_ref | 额外的 “寄存器”,用于存储在插入新记录时导致唯一键冲突的记录引用。 |
ulonglong data_file_length | 对于使用数据文件的存储引擎,该字段表示数据文件的长度。不使用数据文件的存储引擎会在这个变量中存储所有记录的总长度加上新插入记录可能存放位置的 “空洞” 长度。这个值会显示在SHOW TABLE STATUS 的输出中。 |
ulonglong max_data_file_length | data_file_length 成员所指数据文件的最大可能长度。显示在SHOW TABLE STATUS 的输出中。 |
ulonglong index_file_length | 对于使用索引文件的存储引擎,该字段表示索引文件的长度。不使用索引文件的存储引擎会在此处存放用于存储该表索引的大致内存或磁盘空间量。显示在SHOW TABLE STATUS 的输出中。 |
ulonglong max_index_file_length | 索引文件的最大可能长度。目前,只有MyISAM和ISAM处理器会设置这个值。 |
ulonglong delete_length | 已分配但未使用的字节数。在MyISAM中,它是指已被标记为删除的记录所占用的空间量。显示在SHOW TABLE STATUS 的输出中。 |
ulonglong auto_increment_value | 如果未设置INSERT_ID ,在下次插入未指定自增列值时,将分配给该自增列的值。这个值可以在创建表时使用AUTO_INCREMENT 子句设置,也可以使用ALTER TABLE 设置。 |
ha_rows records | 表中的记录数。由于多版本控制带来的复杂性,InnoDB提供的只是一个估计值。显示在SHOW TABLE STATUS 的输出中。 |
ha_rows deleted | 表中被标记为删除的记录数。 |
ulong raid_chunksize | 与MyISAM表的RAID功能相关。在5.1版本中已移除。 |
ulong mean_rec_length | 记录的平均长度。显示在SHOW TABLE STATUS 的输出中。 |
time_t create_time | 表的创建时间。显示在SHOW TABLE STATUS 的输出中。 |
time_t check_time | 上次使用CHECK TABLE 检查表的时间。显示在SHOW TABLE STATUS 的输出中。 |
time_t update_time | 表的上次更新时间。显示在SHOW TABLE STATUS 的输出中。 |
key_range save_end_range | 在handler::read_range_first() 成员方法中使用的一个存储变量。 |
key_range *end_range | 在一些与根据键值范围读取记录相关的成员方法中使用的存储变量。 |
KEY_PART_INFO *range_key_part | 在read_range_first() 成员方法中使用的存储变量。 |
int key_compare_result_on_equal | 在read_range_first() 和compare_key() 成员方法中使用的存储变量。如果实际键值与被比较的键值相等,该变量包含compare_key() 应返回的结果。根据遍历键值范围的模式,优化器可能会认为将相等值视为小于或大于被比较值更方便,并在read_range_first() 中提出这样的要求。例如,在搜索范围键< const 时,即使键值等于const ,compare_key() 也会返回 “大于” 的结果。这简化了上层的边界检查。 |
bool eq_range | 在read_range_first() 和read_range_next() 成员方法中使用的存储变量。如果范围的起始值和结束值相同,则设置为true 。 |
uint errkey | 包含最后一个发生错误的键的编号。通常,该错误是试图创建唯一键的重复键值。 |
uint sortkey | 如果存在,记录按其进行物理排序的键编号。如果不存在这样的键,则设置为255。目前未使用。 |
uint key_used_on_scan | 如果存在,上次用于扫描记录的键编号。如果不存在这样的键,则设置为MAX_KEY 。 |
uint active_index | 当前选定键的编号。如果未选择任何键,则设置为MAX_KEY 。 |
uint ref_length | 存储在ref 成员中的值的长度。 |
uint block_size | 用于此表的键块大小。 |
uint raid_type | 与MyISAM表的RAID功能相关。在5.1版本中已移除。 |
uint raid_chunks | 与MyISAM表的RAID功能相关。在5.1版本中已移除。 |
FT_INFO *ft_handler | 全文键操作描述符。目前仅适用于MyISAM表。 |
enum {NONE=0, INDEX, RND} inited | 指示handler 对象是否已被初始化为读取键(INDEX )、扫描表(RND )或根本未初始化(NONE )。调用ha_init_index() 会将此值设置为INDEX ,调用ha_init_rnd() 会将其设置为RND 。清理并将值重置为NONE 分别由ha_end_index() 和ha_end_rnd() 执行。 |
bool auto_increment_column_changed | 由update_auto_increment() 设置,用于指示上次操作是否导致自增列值发生更改。 |
bool implicit_emptied | 由MEMORY处理器设置,用于指示内存表在服务器重启期间是否被清空。此信息对于正确的复制日志记录是必要的。 |
处理程序(handler)的方法如表7-2所示。 表7-2 handler方法
方法定义 | 方法描述 |
---|---|
int ha_open(const char *name, int mode, int test_if_locked) | 打开由name 参数指定的表。该参数是包含表定义的.frm 文件的路径,不包含.frm 扩展名。例如,如果表的.frm 文件是./test/t1.frm ,则参数字符串为./test/t1 。其余参数会传递给open() 函数,并由特定的存储引擎进行解释。成功时返回0,失败时返回非零错误代码。 |
void update_auto_increment() | 确定要插入的自增值,并将其存储在自增字段描述符中。 |
virtual void print_error(int error, myf errflag) | 向错误日志打印错误消息。此方法有一个通用实现,用于处理最常见的错误。如果遇到未知错误代码,则通过get_error_message() 查找消息。error 参数是错误代码,errflag 参数传递给mysys/my_error.c 中的my_error() 函数,通常为0。 |
virtual bool get_error_message(int error, String *buf) | 如果print_error() 不知道存储引擎特定的错误消息,则查找该消息。error 参数是错误代码,buf 参数是存储结果消息的String 缓冲区的地址。如果存储引擎中的错误是临时的,则返回true ,否则返回false 。 |
uint get_dup_key(int error) | 返回与最后一个重复键错误相关联的键的编号。如果参数包含与重复键错误无关的错误代码,则返回(uint)-1 。 |
void change_table_ptr(TABLE *table_arg) | 将表成员设置为参数提供的值。 |
virtual double scan_time() | 返回扫描整个表所需的估计块读取操作数。 |
virtual double read_time(uint index, uint ranges, ha_rows rows) | 返回使用编号为index 的键从ranges 个范围读取rows 数量的行所需的估计块读取操作数。 |
virtual const key_map *keys_to_use_for_scanning() | 通常情况下,MySQL优化器在不使用键的情况下扫描表,因为对普通数据文件的全表扫描比遍历B树索引更快。然而,一些存储引擎可能以某种方式组织数据,使得在全表扫描时遍历键更有利。此方法返回一个键映射,其中为可用于扫描表的键设置了相应的位。 |
virtual bool has_transactions() | 如果存储引擎支持事务,则返回true 。默认实现返回false 。自5.1版本起该方法不是虚函数。 |
virtual uint extra_rec_buf_length() | openfrm() 函数(位于sql/table.cc )分配一个临时记录缓冲区,用于在表描述符中存储当前记录。缓冲区的大小是记录的逻辑长度,可能还会加上一些特定于存储引擎的额外保留长度。此方法返回这个额外长度的值。 |
virtual ha_rows estimate_rows_upper_bound() | 返回扫描表时可能检查的最大记录数。 |
virtual const char *index_type(uint key_number) | 返回指向由参数指定的索引的文本描述的指针。 |
int ha_index_init(uint idx) | 初始化存储引擎,以便对由参数指定的键执行操作。成功时返回0,失败时返回非零值。 |
int ha_index_end() | 在存储引擎中完成键操作后执行清理工作。必须在ha_index_init() 之后调用。成功时返回0,失败时返回非零值。 |
int ha_rnd_init(bool scan) | 初始化存储引擎以进行基于位置的记录读取。参数指定是否要执行全表扫描。成功时返回0,失败时返回非零值。 |
int ha_rnd_end() | 在基于位置的读取操作后进行清理。必须在ha_rnd_init() 之后调用。成功时返回0,失败时返回非零值。 |
int ha_index_or_rnd_end() | 根据之前进行的初始化操作,调用ha_index_end() 或ha_rnd_end() 。成功时返回0,失败时返回非零值。 |
uint get_index(void) const | 返回当前选择的索引编号。 |
virtual int open(const char *name, int mode, uint test_if_locked)=0 | 执行打开表的实际工作(与ha_open() 不同,ha_open() 只是一个包装函数)。name 参数是.frm 文件的路径,不包含扩展名。其余参数包含标志,用于指定初始化内容以及在表文件被锁定时的操作。这些标志大多对MyISAM存储引擎有意义。成功时返回0,失败时返回非零错误代码。请注意,此方法是纯虚函数,必须在子类中实现。 |
virtual int close(void)=0 | 关闭表并执行必要的清理工作。必须在open() 之后调用。成功时返回0,失败时返回非零错误代码。请注意,此方法是纯虚函数,必须在子类中实现。 |
virtual int write_row(byte *buf) | 将参数指向的记录插入到表中。在处理INSERT 查询时,此调用是所有存储引擎共享的执行栈底层操作。请注意,该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。因此,如果未实现此方法,所有INSERT 查询都会返回错误。 |
virtual int update_row(const byte *old_data, byte *new_data) | 将old_data 指向的记录更新为new_data 指向的内容。在处理UPDATE 查询时,此调用是所有存储引擎共享的执行栈底层操作。请注意,该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。因此,如果未实现此方法,所有UPDATE 查询都会返回错误。 |
virtual int delete_row(const byte *buf) | 从表中删除参数指向的记录。在处理DELETE 查询时,此调用是所有存储引擎共享的执行栈底层操作。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_read(byte *buf, const byte *key, uint key_len, enum ha_rkey_function find_flag) | 根据key 和key_len 的值,将键光标定位到第一个匹配的键上,如果存在匹配项,则将记录读入buf 。匹配操作根据find_flag 指定的查找方法进行。该操作使用当前活动索引。enum ha_rkey_function 在include/my_base.h 中定义。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_read_idx(byte *buf, uint index, const byte *key, uint key_len, enum ha_rkey_function find_flag) | 与index_read() 相同,只是首先激活由index 参数指定的键。 |
virtual int index_next(byte *buf) | 从活动索引中读取下一条记录到参数指定的缓冲区,并推进活动键光标。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_prev(byte *buf) | 从活动索引中读取上一条记录到参数指定的缓冲区,并将活动键光标向后移动。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_first(byte *buf) | 从活动索引中读取第一条记录到参数指定的缓冲区,并将活动键光标定位到紧随其后的位置。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_last(byte *buf) | 从活动索引中读取最后一条记录到参数指定的缓冲区,并将活动键光标定位到紧靠前的位置。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_next_same(byte *buf, const byte *key, uint keylen) | 从当前活动记录开始,将与上一条读取记录具有相同键值的下一条记录读入buf 指向的缓冲区。由于一些存储引擎不存储最后读取的键的值,因此使用key 和keylen 参数来提醒它们。成功时,活动键光标会推进并返回0;失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int index_read_last(byte *buf, const byte *key, uint key_len) | 将通过最后一个与key 和key_len 的值匹配的键值找到的记录读入buf ,并将光标定位到该记录紧靠前的位置。成功时返回0,失败时返回非零错误代码。该方法有一个默认实现,返回HA_ERR_WRONG_COMMAND 。 |
virtual int read_range_first(const key_range *start_key, const key_range *end_key, bool eq_range, bool sorted) | 将由start_key 和end_key 参数指定的范围内的第一条记录读入table->record[0] 缓冲区。范围边界会被保存,以供read_range_next() 使用。eq_range 参数指示范围的起始和结束值是否相同。虽然可以通过检查start_key 和end_key 获得此信息,但调用者通常已经进行了检查,如果传递此信息可以节省一些CPU周期。sorted 参数表示调用者是否期望按键顺序接收记录。key_range 类型在include/my_base.h 中定义。该方法成功时返回0,失败时返回非零错误代码。默认实现会在start_key 为0时调用index_first() 读取第一个值,否则调用index_read() 读取范围起始处的第一个匹配键值。compare_key() 用于测试读取的键是否仍在范围内。目前,只有NDB集群引擎(见sql/ha_ndbcluster.cc )重新实现了此方法。 |
virtual int read_range_next() | 从当前范围读取下一条记录到table->record[0] 缓冲区。成功时返回0,失败时返回非零错误代码。在该方法的默认实现中,如果eq_range (从read_range_first() 调用中记住的值)设置为true ,则调用index_next_same() 将索引光标定位到与前一个键值相等的下一个键值上;否则,调用index_next() 移动到下一个键值,然后调用compare_key() 检查该值是否在范围内。目前,只有NDB集群引擎(见sql/ha_ndbcluster.cc )重新实现了此方法。 |
int compare_key(key_range *range) | 将当前记录(位于table->record[0] )的键与参数指定的键范围限制值进行比较。如果值相同或range 的值为0,则返回0;如果当前记录键小于范围限制,则返回 -1;如果当前记录键大于范围限制,则返回1。 |
virtual int ft_init() | 为全文本键操作重新初始化存储引擎。当MySQL需要多次重复全文本搜索(例如在连接操作中)时可以调用此方法。目前仅在MyISAM中有效。成功时返回0,否则返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual FT_INFO *ft_init_ext(uint flags, uint inx, const byte *key, uint keylen) | 初始化全文本引擎以进行搜索。目前仅在MyISAM中有效。flags 参数指定搜索模式,inx 参数是索引编号,key 和keylen 参数提供要搜索的键。从5.0版本开始,最后两个参数已被String* 取代。成功时返回指向全文本搜索描述符的指针,失败时返回NULL 。默认实现仅返回NULL 。 |
virtual int ft_read(byte *buf) | 将当前活动全文本键的下一条记录读入参数指向的缓冲区。目前仅在MyISAM中有效。成功时返回0,错误时返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual int rnd_next(byte *buf)=0 | 在顺序表扫描期间,将下一条记录读入buf 指向的缓冲区,并推进顺序扫描光标。成功时返回0,失败时返回非零错误代码。请注意,此方法是纯虚函数,必须在子类中实现。 |
virtual int rnd_pos(byte *buf, byte *pos)=0 | 将由pos 指定的记录读入buf 。pos 的解释取决于存储引擎。MyISAM使用记录的数据文件偏移量,InnoDB使用主键值,MEMORY使用记录的内存地址。成功时返回0,失败时返回非零错误代码。请注意,此方法是纯虚函数,必须在子类中实现。 |
virtual int read_first_row(byte *buf, uint primary_key) | 从表中检索一条任意选择的记录,并将其放入buf 参数指向的缓冲区中。primary_key 参数会影响选择记录的方法。目前,默认实现使用两种方法选择此记录。第一种方法是扫描表并返回第一条未标记为删除的记录,另一种方法是选择编号为primary_key 的键中的第一条记录。如果标记为删除的记录少于10条,或者primary_key 参数大于或等于MAX_KEY ,则使用第一种方法;否则选择第二种方法。目前没有存储引擎重新实现此方法。成功时返回0,失败时返回非零错误代码。 |
virtual int restart_rnd_next(byte *buf, byte *pos) | 目前仅在MyISAM中有效,此方法是rnd_pos() 的别名。默认实现返回HA_ERR_WRONG_COMMAND 。目前,此方法仅在处理临时表上的SELECT DISTINCT 时,由从结果集中删除重复项的代码调用一次。未来此方法可能会被重命名或删除。 |
表7-2. handler方法(续)
方法定义 | 方法描述 |
---|---|
virtual int rnd_same(byte *buf, uint inx) | 重新将当前记录读入buf ,如果idx 的值大于或等于0,可能会使用键编号idx 。成功返回0,出错返回非零错误代码。目前这个方法从未被调用,也没有存储引擎实现它。默认实现返回HA_ERR_WRONG_COMMAND 。不过,MEMORY和MyISAM存储引擎在heap/hp_rsame.c 和myisam/mi_rsame.c 中有实现该方法的相关代码。 |
virtual ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key) | 返回在键编号inx 中,与受min_key 和max_key 限制的键值匹配的记录估计数量。默认实现返回10。如果返回错误的值,最糟糕的情况是优化器会选择不太优的键,或者根本不使用键。 |
virtual void position(const byte *record)=0 | 将当前记录的唯一引用值存储在ref 成员中。对于MyISAM表,这个值是记录在数据文件中的位置,因此该方法以此命名。有些存储引擎可能不记得最后一条记录的唯一引用值,可能需要查看由参数提供的实际记录。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual void info(uint flag)=0 | 根据参数的值更新此对象的各种统计变量的值。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual int extra(enum ha_extra_function operation) | 为存储引擎提供使用某些特殊优化的提示。例如,如果参数是HA_EXTRA_KEYREAD ,对键的读操作可能只检索记录中包含在键中的部分。成功返回0,否则返回非零错误代码。默认实现只返回0,因为提示可以安全地忽略。 |
virtual int extra_opt(enum ha_extra_function operation, ulong cache_size) | 与extra() 类似,但它允许调用者向请求的操作传递一个参数(cache_size )。主要用于控制各种类型I/O的缓存大小。 |
virtual int reset() | 围绕handler:extra(HA_EXTRA_RESET) 的包装器。释放之前extra() 调用分配的资源,并将存储引擎的操作模式重置为默认值。 |
virtual int external_lock(THD *thd, int lock_type)=0 | MySQL会在每个语句开始时,对语句中使用的每个表调用此方法一次。如果启用了外部锁定选项,MyISAM只是通过操作系统锁定键文件,因此这个选项有这样的历史名称。事务性存储引擎将其用作启动事务和在必要时执行其他初始化的钩子函数。成功返回0,出错返回非零错误代码。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual void unlock_row() | 在UPDATE 或DELETE 操作中,对于不匹配WHERE 子句的每一行,调用此方法以移除不必要的行锁。InnoDB用它来清除在半一致性读模式下读取的行上的锁(如果当前版本被另一个事务锁定,则读取最后提交的版本)。 |
virtual int start_stmt(THD *thd) | 在通过LOCK TABLES 发起事务开始时调用,为事务性存储引擎提供一个注册事务开始的机会。成功返回0,出错返回非零错误代码。默认实现不执行任何操作并报告成功。 |
virtual int delete_all_rows() | 一次性从表中删除所有行。这是一个可选的优化操作。如果不支持,会通过多次调用delete_row() 来清空表。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual longlong get_auto_increment() | 返回自动增量键的下一个值。有趣的是,尽管这个方法有一个相当复杂的默认实现,但大多数现有的存储引擎都重新实现了它。 |
virtual int check(THD* thd, HA_CHECK_OPT* check_opt) | 检查表是否存在结构错误。在发出CHECK TABLE 命令时调用。thd 参数是当前线程描述符。check_opt 参数指向一个描述操作选项的结构。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual int restore(THD* thd, HA_CHECK_OPT* check_opt) | 根据.frm 文件和数据文件重新创建索引文件。目前仅在MyISAM中实现。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。该方法将在5.2版本中移除。 |
virtual int repair(THD* thd, HA_CHECK_OPT* check_opt) | 修复损坏的表。在发出REPAIR TABLE 命令时调用。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual int optimize(THD* thd, HA_CHECK_OPT* check_opt) | 将表重构为对典型查询来说最优的形式。在发出OPTIMIZE TABLE 命令时调用。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual int analyze(THD* thd, HA_CHECK_OPT* check_opt) | 更新优化器使用的索引统计信息。在发出ANALYZE TABLE 命令时调用。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual int assign_to_keycache(THD* thd, HA_CHECK_OPT* check_opt) | 将此表的键分配给check_opt 结构中指定的键缓存。在发出CACHE INDEX 命令时调用。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual int preload_keys(THD* thd, HA_CHECK_OPT* check_opt) | 将此表的键加载到check_opt 结构中指定的缓存中。在发出CACHE INDEX 命令时调用。成功返回0,失败返回非零错误代码。默认实现返回HA_ADMIN_NOT_IMPLEMENTED 。 |
virtual bool check_and_repair(THD *thd) | 检查表是否损坏,并在必要时进行修复。成功返回0,出错返回1。默认实现只返回1。 |
virtual int dump(THD* thd, int fd = –1) | 将表数据以特定于存储引擎的格式写入由fd 指定的文件句柄。如果fd 小于0,则将数据写入与thd 相关联的网络连接。转储格式必须能被net_read_dump() 理解。该方法用于LOAD DATA FROM MASTER 。目前仅在MyISAM中实现。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。该方法将在5.2版本中移除。 |
virtual int disable_indexes(uint mode) | 禁用表中键的使用。在发出DISABLE KEYS 命令时调用。通常在对表进行大量更新之前,在持有表锁的情况下使用。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual int enable_indexes(uint mode) | 重新启用表中键的使用。在发出ENABLE KEYS 命令时调用。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual int indexes_are_disabled(void) | 如果此表中的索引已被禁用,则返回1,否则返回0。 |
virtual void start_bulk_insert (ha_rows rows) | 指示存储引擎启用批量插入优化。MySQL在向表中插入大量行之前调用它。MyISAM通过在内存中缓存键值并按键顺序将它们插入B树索引来优化批量插入。默认实现不执行任何操作。 |
virtual int end_bulk_insert() | 在批量插入批次结束时调用。成功返回0,否则返回非零错误代码。默认实现只返回0。 |
virtual int discard_or_import_tablespace(my_bool discard) | InnoDB用于对为此表分配的表空间执行操作的方法。discard 操作准备表空间以便从备份中导入数据。import 操作在要恢复的表空间文件被复制到指定位置后,从备份中恢复数据。在执行ALTER TABLE... DISCARD TABLESPACE 或ALTER TABLE... IMPORT TABLESPACE 时调用。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual int net_read_dump(NET* net) | 从参数指定的网络连接读取表数据,并以一种使得调用repair() 足以使表进入一致状态的方式存储数据。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。将在5.2版本中移除。 |
virtual char *update_table_comment (const char * comment) | 用于在SHOW TABLES 中,在Comment 列显示有关表的一些额外信息。返回一个指向包含更新后的注释值的字符串的指针。注意,如果返回的值与参数不同,调用者会假定新指针是用my_malloc() 分配的,并在使用后用my_free() 释放它。InnoDB是唯一提供自己实现的存储引擎。默认实现只返回参数的值。 |
virtual void append_create_info (String *packet) | 将特定于存储引擎的额外信息追加到参数指定的String 对象中。用于生成SHOW CREATE TABLE 的输出。默认实现不执行任何操作。 |
virtual char* get_foreign_key_create_info() | 返回一个指向包含CREATE TABLE 语句中创建外键部分的字符串的指针。用于生成SHOW CREATE TABLE 的输出。默认实现返回0。 |
virtual uint referenced_by_foreign_key() | 如果与此对象关联的表被某个外键引用,则返回1,否则返回0。默认实现返回0。 |
virtual void init_table_handle_for_HANDLER() | 为后续的HANDLER 命令准备表。HANDLER 命令通过SQL为一些存储引擎操作提供低级接口。默认实现不执行任何操作。 |
virtual void free_foreign_key_create_info(char* str) | 如果需要,释放get_foreign_key_create_info() 返回的指针。默认实现不执行任何操作。 |
virtual const char *table_type( ) const =0 | 返回一个指向包含存储引擎名称的字符串的指针。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual const char **bas_ext( ) const =0 | 返回一个指向字符指针数组的指针,数组中的元素是此存储引擎存储数据和键的文件的文件扩展名。数组的最后一个元素是0。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual ulong table_flags(void) const =0 | 返回此存储引擎功能的位掩码。功能在sql/handler.h 中定义。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual ulong index_flags(uint idx, uint part, bool all_parts) const =0 | 返回由参数指定的键或其部分的功能位掩码。功能在sql/handler.h 中定义。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual ulong index_ddl_flags (KEY *wanted_index) const | 返回给定键在创建或删除方面的功能位掩码。默认实现返回DDL_SUPPORT ,这意味着存储引擎支持给定定义的索引,但不能将其添加到现有表中(MySQL将创建一个带有此索引的新表并复制数据)。 |
virtual int add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys) | 向表中添加键集合。第二个参数是键定义数组的起始位置,第三个参数是其大小。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
virtual int drop_index(TABLE *table_arg, uint *key_num, uint num_of_keys) | 从参数指定的表中删除键。成功返回0,出错返回非零错误代码。默认实现返回HA_ERR_WRONG_COMMAND 。 |
uint max_record_length( ) const | 返回可能的最大记录长度。限制要么是存储引擎本身支持的长度,要么是核心代码施加的限制,取较小值。 |
uint max_keys( ) const | 返回每个表可能的最大键数。限制要么是存储引擎本身支持的数量,要么是核心代码施加的限制,取较小值。 |
uint max_key_parts( ) const | 返回一个键可以包含的最大列数或列前缀数。限制要么是存储引擎本身支持的数量,要么是核心代码施加的限制,取较小值。 |
uint max_key_length( ) const | 返回可能的最大键长度。限制要么是存储引擎本身支持的长度,要么是核心代码施加的限制,取较小值。 |
uint max_key_part_length( ) const | 返回可能的最大键部分长度。限制要么是存储引擎本身支持的长度,要么是核心代码施加的限制,取较小值。 |
virtual uint max_supported_record_length( ) const | 返回此存储引擎对记录长度施加的限制。 |
virtual uint max_supported_keys( ) const | 返回此存储引擎对键数量施加的限制。 |
virtual uint max_supported_key_parts( ) const | 返回此存储引擎对键部分数量施加的限制。 |
virtual uint max_supported_key_length( ) const | 返回此存储引擎对键长度施加的限制。 |
virtual uint max_supported_key_part_length( ) const | 返回此存储引擎对键部分长度施加的限制。 |
virtual uint min_record_length (uint options) const | 返回此存储引擎对记录长度施加的下限。默认实现返回1。 |
virtual bool low_byte_first( ) const | 如果此存储引擎记录的原生字节序是小端序,则返回1;否则返回0。默认实现返回1。 |
virtual uint checksum( ) const | 返回此表的实时校验和。默认实现返回0。 |
virtual bool is_crashed( ) const | 如果表被标记为已崩溃,则返回1。如果CHECK TABLE 或常规的读/写操作发现问题,就会出现这种情况。然后,表会通过被标记为已崩溃而实际上离线。成功运行REPAIR TABLE 会移除该标记。 |
virtual bool auto_repair( ) const | 如果存储引擎支持自动修复损坏的表,则返回1。目前只有MyISAM具有此功能。 |
virtual int rename_table(const char *from, const char *to) | 将由from 指定的表移动到to 指定的路径。参数是去掉.frm 扩展名的表定义文件的路径。默认实现会遍历bas_ext() 返回的所有可能扩展名,并重命名匹配的文件。成功返回0,出错返回非零错误代码。 |
virtual int delete_table(const char *name) | 删除由name 指定的表。参数是去掉.frm 扩展名的表定义文件的路径。默认实现会遍历bas_ext() 返回的所有可能扩展名,并删除匹配的文件。成功返回0,出错返回非零错误代码。 |
virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *info)=0 | 使用表描述符form 和创建信息描述符info 创建由name 指定的表。成功返回0,出错返回非零错误代码。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual uint lock_count(void) const | 返回存储此表的锁描述符所需的常规锁描述符块的数量。在大多数情况下,只需要一个锁描述符块,MERGE表除外。MERGE表每个组件表都需要一个块。默认实现返回1。 |
virtual THR_LOCK_DATA **store_lock (THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type)=0 | 将与此表关联的锁描述符的位置存储在to 指示的地址处。其他参数提供当前线程描述符的值和锁的类型,以便存储引擎出于内部目的了解这些值。此方法的主要目的是允许存储引擎在存储锁之前修改锁。行级锁定存储引擎用它来防止表锁管理器对表施加过多的锁。成功返回to 的值,失败返回0。注意,该方法是纯虚函数,必须在子类中实现。 |
virtual uint8 table_cache_type( ) | 返回与查询缓存相关的选项的位掩码。默认实现返回HA_CACHE_TBL_NONTRANSACT ,表示无论是否有事务在进行,都允许缓存。返回HA_CACHE_TBL_TRANSACT 表示只要表不参与任何活动事务,就允许缓存。HA_CACHE_TBL_ASKTRANSACT 意味着查询缓存会询问存储引擎每个表是否可以缓存。然后,存储引擎可以使用其事务可见性规则来决定。 |
virtual const COND *cond_push (const COND *cond) | 供能够过滤掉不匹配WHERE 子句部分记录的存储引擎使用。最初是为NDB存储引擎创建的,该引擎可能将记录存储在远程节点上,并且可以从在内部处理WHERE 子句的部分内容中受益。由参数表示的WHERE 子句的请求部分被推送到此存储引擎实例的表达式栈上。返回一个新的表达式树,调用者必须计算该树以确定记录是否确实匹配WHERE 子句。如果过滤在存储引擎内部完全完成,则返回NULL 。默认实现直接返回参数,不执行任何其他操作。 |
virtual void cond_pop( ) | 从存储引擎条件栈的顶部移除顶部条件。默认实现不执行任何操作。 |
virtual void try_semi_consistent_read (bool flag) | 用于告知存储引擎,如果当前版本被另一个事务锁定,读取记录的最后提交版本是可以接受的。InnoDB在UPDATE 和DELETE 查询中使用它来避免不必要的锁。 |
virtual bool was_semi_consistent_read( ) | 当存储引擎告知优化器不要更新最后读取的记录,因为它没有读取当前版本时,返回true 。 |
# handlerton
在4.1版本之前,对handler
类进行子类化是存储引擎连接到核心代码并与之交互的唯一方法。如果优化器需要对存储引擎执行某些操作,它会调用当前表的handler
虚方法。然而,随着各种存储引擎集成工作的推进,很明显仅通过handler
方法进行接口交互是不够的。因此,引入了handlerton
的概念。
handlerton
是一个C结构体,主要由回调函数指针组成。它在sql/handler.h
中定义。调用这些回调函数来处理涉及给定存储引擎的某些事件。例如,当提交事务、发生保存点或关闭连接时,可能需要执行一些特殊操作,在这种情况下,handlerton
将拥有指向相应回调函数的指针。表7-3记录了handlerton
结构体的成员。
表7-3. handlerton结构体的成员
定义 | 描述 |
---|---|
const int interface_version | handlerton 接口版本号,应设置为MYSQL_HANDLERTON_INTERFACE_VERSION 。 |
const char *name | 存储引擎的名称。 |
SHOW_COMP_OPTION state | 用于在SHOW STORAGE ENGINES 的输出中正确显示Support 列。通常应设置为SHOW_OPTION_YES 。 |
const char *comment | SHOW STORAGE ENGINES 输出中Comment 列的值。 |
enum legacy_db_type db_type | 用于.frm 文件中,确定关联表的存储引擎类型。 |
bool (*init)( ) uint slot | 初始化存储引擎的函数。 |
uint savepoint_offset | 供MySQL内部使用。最初应设置为0。包含保存点存储区域的偏移量。最初应设置为保存点结构体的大小。 |
int (*close_connection)(THD *thd) | 在连接关闭时执行特定于存储引擎的清理操作的函数。 |
int (*savepoint_set)(THD *thd, void *sv) | 处理保存点的函数。 |
int (*savepoint_rollback) (THD *thd, void *sv) | 处理ROLLBACK TO SAVEPOINT 的函数。 |
int (*savepoint_release) (THD *thd, void *sv) | 处理RELEASE SAVEPOINT 的函数。 |
int (*commit)(THD *thd, bool all) | 处理COMMIT 的函数。 |
int (*rollback)(THD *thd, bool all) | 处理ROLLBACK 的函数。 |
int (*prepare)(THD *thd, bool all) | 处理XA PREPARE 的函数。 |
int (*recover)(XID *xid_list, uint len) | 处理XA RECOVER 的函数。 |
int (*commit_by_xid)(XID *xid) | 处理XA COMMIT 的函数。 |
int (*rollback_by_xid)(XID *xid) | 处理XA ROLLBACK 的函数。 |
void *(*create_cursor_read_view)( ) | 打开游标。 |
void (*set_cursor_read_view)(void *) | 从游标中获取数据。 |
void (*close_cursor_read_view)(void *) | 关闭游标。 |
handler *(*create)(TABLE_SHARE *table) | 创建表的函数。 |
void (*drop_database)(char* path) | 删除数据库的函数。 |
int (*panic)(enum ha_panic_function flag) | 处理紧急关闭的函数。 |
int (*start_consistent_snapshot)(THD *thd) | 处理START TRANSACTION WITH CONSISTENT SNAPSHOT 的函数。 |
bool (*flush_logs)( ) | 处理FLUSH LOGS 的函数。 |
bool (*show_status)(THD *thd, stat_print_fn *print, enum ha_stat_type stat) | 处理SHOW ENGINE STATUS 的函数。 |
uint (*partition_flags)( ) | 返回一组标志,指示存储引擎处理跨不同文件系统分区的表数据的能力。 |
uint (*alter_table_flags)(uint flags) | 返回一组标志,指示存储引擎在ALTER TABLE 操作中的不同能力。 |
int (*alter_tablespace)(THD *thd, st_alter_tablespace *ts_info) | 处理ALTER TABLESPACE 的函数。 |
int (*fill_files_table)(THD *thd, struct st_table_list *tables, class Item *cond) | 为SELECT * FROM information_schema.files 提供数据的函数。 |
uint32 flags | 存储引擎功能的位掩码。 |
int (*binlog_func)(THD *thd, enum_binlog_func fn, void *arg) | 处理复制日志操作的函数。 |
void (*binlog_log_query)(THD *thd, enum_binlog_command binlog_command, const char *query, uint query_length, const char *db, const char *table_name) | 每次将查询写入复制日志时调用的函数。 |
int (*release_temporary_latches)(THD *thd) | 为InnoDB创建的一个特殊回调函数,用于在向客户端发送记录时避免死锁。 |
# 向MySQL添加自定义存储引擎
向MySQL添加自定义存储引擎有诸多原因:
- 你拥有一个遗留的专有数据库,想要为其提供SQL/ODBC接口。
- 你在性能或数据安全方面存在一些特殊要求,而现有的存储引擎都无法满足。
- 你创建了一个底层数据存储和检索模块,自认为它将独树一帜,但你不想(或无法)为其编写配套的SQL优化器。
- 你现有的专有SQL优化器无法满足需求,你想为自己的存储引擎寻找一个更优的优化器。
- 你只是想更深入地了解MySQL的内部机制。
下面我们通过一个示例来进行说明。我们的存储引擎将为逗号分隔值(CSV)文本文件提供一个只读的SQL接口。在4.1及更早版本中,存储引擎的集成需要对源代码进行大量修改。而在5.1版本中,这一过程变得更加简洁。如果你正在编写自定义存储引擎,可以根据自身需求,选择更成熟的4.1版本代码库,或者(在撰写本文时)选择最前沿的5.1版本。我将提供针对4.1和5.1版本的操作说明。为简洁起见,我不会提供其他MySQL版本的相关说明。需要将存储引擎集成到其他版本的用户,建议在对应版本的源代码树中(不区分大小写)搜索字符串“blackhole”,并参照黑洞(blackhole)存储引擎的模式进行操作。
# MySQL 4.1版本的集成说明
本节的说明是基于MySQL 4.1.11的源代码编写的,但在4.1的后续版本中也应该同样适用。我们假设你已经下载并解压了MySQL源代码发行版。
- 从本书网站的示例中,将
ha_csv_4_1.cc
和ha_csv4_1.h
文件复制到sql/
目录中,并分别重命名为ha_csv.cc
和ha_csv.h
。如果你没有网络访问权限,可以从示例7 - 1和7 - 2中复制。这两个文件定义并实现了我们的存储引擎类。 - 在
sql/Makefile.am
中,将ha_csv.h
添加到noinst_HEADERS
变量中,将ha_csv.cc
添加到mysqld_SOURCES
中。这一步是为了将这些文件纳入编译框架。 - 要更新Makefile文件,在MySQL源代码树的顶级目录中运行以下一组命令:
$ autoconf
$ automake
$ ./configure --prefix=/usr
2
3
- 现在,你需要对核心代码进行一些修改,使其能够识别新的存储引擎。在
sql/handler.cc
文件顶部的其他包含指令中添加以下这一行:
#include "ha_csv.h"
- 仍然在
sql/handler.cc
文件中,使用以下成员扩展sys_table_types[]
数组(数组中除最后一个元素外的任何位置都可以):
{"OREILLY_CSV", &have_yes,
"Example CSV Engine - Understanding MySQL Internals", DB_TYPE_OREILLY_CSV}
2
- 在
sql/handler.cc
文件中,使用以下代码扩展get_new_handler()
函数中的switch
语句:
case DB_TYPE_OREILLY_CSV:
return new ha_csv(table);
2
- 在
sql/handler.h
文件中,向enum db_type
添加一个新成员DB_TYPE_OREILLY_CSV
。除最后一个位置外,其他任何位置都可以。 - 在顶级目录中运行
make
命令。构建完成后,你将在sql/mysqld
中得到一个支持新存储引擎的二进制文件。
示例7 - 1 MySQL 4.1的ha_csv.h存储引擎头文件
/* 告知GCC这是一个头文件 */
#ifdef USE_PRAGMA_INTERFACE
#pragma interface
#endif
/* CSV行可能很长,以512字节为一块进行读取 */
#define CSV_READ_BLOCK_SIZE 512
/*
遵循其他存储引擎的惯例,我们将所有底层信息放在一个单独的结构中。
*/
struct CSV_INFO {
char fname[FN_REFLEN+1];
int fd;
};
/* 现在定义处理程序类 */
class ha_csv: public handler
{
protected:
/* 底层存储引擎数据 */
CSV_INFO* file;
/* 用于表锁管理器的锁结构 */
THR_LOCK_DATA lock;
THR_LOCK thr_lock;
/* 表扫描游标 */
my_off_t pos;
/* 用于读取CSV行块的缓冲区 */
char read_buf[CSV_READ_BLOCK_SIZE];
/* 用于解析字段值的缓冲区 */
String field_buf;
/* 详见实现文件中的注释 */
int fetch_line(byte* buf);
/* 为顺序扫描初始化存储引擎对象 */
int rnd_init(bool scan)
{
pos = 0;
records = 0;
return 0;
}
public:
/* 构造函数 */
ha_csv(TABLE* table): handler(table), file(0) {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
示例7 - 1 MySQL 4.1的ha_csv.h存储引擎头文件(续)
/* 析构函数 */
~ha_csv( )
{
}
/* 详见实现文件中对以下方法的注释 */
int open(const char *name, int mode, uint test_if_locked);
int close(void);
int rnd_next(byte *buf);
int rnd_pos(byte * buf, byte *pos);
void position(const byte *record);
void info(uint flags);
int external_lock(THD *thd, int lock_type);
const char **bas_ext( ) const;
ulong table_flags(void) const;
ulong index_flags(uint idx, uint part, bool all_parts) const;
int create(const char *name, TABLE *form, HA_CREATE_INFO *info);
THR_LOCK_DATA **store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type);
/*
返回用于`SHOW TABLE STATUS`输出中的存储引擎类型字符串
*/
const char *table_type( ) const
{
return "OREILLY_CSV";
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
示例7 - 2 MySQL 4.1的ha_csv.cc存储引擎实现文件
/* 告知GCC我们正在处理实现源文件 */
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation
#endif
/* sql/目录中的主要包含文件 */
#include "mysql_priv.h"
/* 我们这个存储引擎自己的头文件 */
#include "ha_csv.h"
/* 供ha_csv::bas_ext()使用 */
static const char* csv_ext[]= {".csv",0};
/* 表打开时调用 */
int ha_csv::open(const char *name, int mode, uint test_if_locked)
{
/* 初始化锁管理器使用的锁结构 */
thr_lock_init(&thr_lock);
thr_lock_data_init(&thr_lock,&lock,NULL);
/* 为数据文件描述符分配内存 */
file= (CSV_INFO*)my_malloc(sizeof(CSV_INFO),MYF(MY_WME));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
示例7 - 2 MySQL 4.1的ha_csv.cc存储引擎实现文件(续)
if (!file)
{
return 1;
}
/* 将表名转换为数据文件名 */
fn_format(file->fname, name, "", ".csv",
MY_REPLACE_EXT|MY_UNPACK_FILENAME);
/*
打开文件,并将文件句柄ID保存到数据文件描述符结构中。
*/
if ((file->fd = my_open(file->fname,mode,MYF(0))) < 0)
{
int error = my_errno;
close( );
return error;
}
/* 读取操作从文件开头开始 */
pos = 0;
return 0;
}
/* 表关闭时调用 */
int ha_csv::close(void)
{
/*
清理锁结构,关闭文件句柄,并释放数据文件描述符的内存。
*/
thr_lock_delete(&thr_lock);
if (file)
{
if (file->fd >= 0)
{
my_close(file->fd, MYF(0));
}
my_free((gptr)file,MYF(0));
file = 0;
}
return 0;
}
/*
从当前位置读取一行数据到调用者提供的记录缓冲区中。
*/
int ha_csv::fetch_line(byte* buf)
{
/*
读取行数据到缓冲区时,跟踪文件中的当前偏移量。
从当前读取游标位置开始。
*/
my_off_t cur_pos = pos;
/*
我们将用它遍历表字段指针数组,以便将解析后的数据以正确的格式存储到正确的位置。
*/
Field** field = table->field;
/*
在解析过程中用于记住前一个字符。256这个不可能的值表示最后一个字符不存在(我们在第一个字符处),或者其值无关紧要。
*/
int last_c = 256;
/* 如果在引号字符串内则设置为1 */
int in_quote = 0;
/* 到目前为止在这一行中看到的字节数 */
uint bytes_parsed = 0;
/* 循环中断标志 */
int line_read_done = 0;
/* 截断字段值缓冲区 */
field_buf.length(0);
/* 尝试读取一整行 */
for (;!line_read_done;)
{
/* 读取一块数据到本地缓冲区并处理错误 */
char buf[CSV_READ_BLOCK_SIZE];
uint bytes_read = my_pread(file->fd,buf,sizeof(buf),cur_pos,MYF(MY_WME));
if (bytes_read == MY_FILE_ERROR)
{
return HA_ERR_END_OF_FILE;
}
if (!bytes_read)
{
return HA_ERR_END_OF_FILE;
}
/*
如果执行到这里,说明读取成功。开始解析我们读取的数据。
*/
char* p = buf;
char* buf_end = buf + bytes_read;
/* 对于缓冲区中的每个字节 */
for (;p < buf_end;)
{
char c = *p;
int end_of_line = 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
示例7 - 2 MySQL 4.1的ha_csv.cc存储引擎实现文件(续)
int end_of_field = 0;
int char_escaped = 0;
switch (c)
{
/*
双引号表示带引号字符串的开始或结束,除非它被转义。
*/
case '"':
if (last_c == '"' || last_c == '\\')
{
field_buf.append(c);
char_escaped = 1;
/*
当我们看到第一个引号时,in_quote会翻转。
然而,后续的引号表示我们仍在带引号的字符串内。
*/
if (last_c == '"')
{
in_quote = 1;
}
}
else
{
in_quote = !in_quote;
}
break;
/*
将反斜杠视为转义字符。
*/
case '\\':
if (last_c == '\\')
{
field_buf.append(c);
char_escaped = 1;
}
break;
/*
除非是带引号的情况,否则在换行符处设置终止标志。
*/
case '\r':
case '\n':
if (in_quote)
{
field_buf.append(c);
}
else
{
end_of_line = 1;
end_of_field = 1;
}
break;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
示例7 - 2 MySQL 4.1的ha_csv.cc存储引擎实现文件(续)
// 逗号表示字段结束,除非在引号内
case ',':
if (in_quote) {
field_buf.append(c);
} else {
end_of_field = 1;
}
break;
// 普通字符直接追加到字段值缓冲区
default:
field_buf.append(c);
break;
2
3
4
5
6
7
8
9
10
11
12
如果到达字段末尾,并且表中存在匹配的字段(如果CSV文件有额外字段,可能不存在匹配字段),将字段值缓冲区的内容转移到相应的Field
对象中。这实际上负责初始化调用者传递给我们的缓冲区参数的正确部分。优化器的内部约定要求,在调用处理程序类的数据检索方法之前,Field
对象的缓冲区指针必须已经设置为指向缓冲区参数的正确区域。
if (end_of_field && *field) {
(*field)->store(field_buf.ptr(),field_buf.length(), system_charset_info);
field++;
field_buf.length(0);
}
2
3
4
5
特殊情况 - 本身被转义的字符不应被视为转义字符。
if (char_escaped)
last_c = 256;
else
last_c = c;
p++;
2
3
4
5
准备在换行时退出循环。
if (end_of_line) {
if (c == '\r')
p++;
line_read_done = 1;
in_quote = 0;
break;
}
2
3
4
5
6
7
块读取/解析循环完成 - 更新计数器。
bytes_parsed += (p - buf);
cur_pos += bytes_read;
2
现在我们完成了行读取/解析。我们还有一些小任务需要完成。
// 初始化记录中的NULL指示标志
memset(buf,0,table->null_bytes);
2
解析后的行可能没有包含所有字段的值。将剩余字段设置为它们的默认值。
for (;*field;field++) {
(*field)->set_default();
}
2
3
将游标移动到下一条记录。
pos += bytes_parsed;
报告成功。
return 0;
在顺序表扫描期间,对每条记录调用一次。
int ha_csv::rnd_next(byte *buf)
{
// 增加SHOW STATUS中Handler_read_rnd_next下显示的全局统计计数器
statistic_increment(ha_read_rnd_next_count,&LOCK_status);
// fetch_line()完成实际工作
int error = fetch_line(buf);
// 如果成功,更新我们对表中记录总数的估计
if (!error)
records++;
// 将fetch_line()返回的代码返回给调用者
return error;
}
2
3
4
5
6
7
8
9
10
11
12
将扫描游标定位到set_pos
指定的位置,并读取该位置的记录。在应用“文件排序(filesort)”技术进行GROUP BY
和ORDER BY
优化时使用。
int ha_csv::rnd_pos(byte * buf, byte *set_pos) {
statistic_increment(ha_read_rnd_count,&LOCK_status);
pos = ha_get_ptr(set_pos,ref_length);
return fetch_line(buf);
}
2
3
4
5
将当前记录的“位置”引用存储到ref
变量中。目前,对于这个存储引擎来说,在某些情况下会调用这个方法,但未来情况可能会改变。
void ha_csv::position(const byte *record) {
ha_store_ptr(ref,ref_length,pos);
}
2
3
更新处理程序对象中的统计变量。
void ha_csv::info(uint flags)
{
// 优化器绝不能认为表中的记录数少于两条,除非实际情况确实如此。报告较少的记录数会使优化器认为它从表中读取的记录数无需超过一条。我们的存储引擎并不总是知道记录的数量,在很多情况下甚至无法做出合理的猜测。为了安全起见并简化操作,我们始终报告至少有两条记录。
if (records < 2)
records = 2;
// 其余变量仅出现在SHOW TABLE STATUS的输出中,不会影响优化器。就本示例而言,它们可以设置为0。
deleted = 0;
errkey = 0;
mean_rec_length = 0;
data_file_length = 0;
index_file_length = 0;
max_data_file_length = 0;
delete_length = 0;
if (flags & HA_STATUS_AUTO)
auto_increment_value = 1;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这本质上是表锁管理器的回调函数,意思是:“我正在内部锁定此表;请处理与该锁相关的、存储引擎特定的操作”。在某些配置中,MyISAM在这种情况下需要锁定文件,因此才有了external_lock()
这个名称。在我们的示例中,无需进行任何操作,只需报告成功即可。
int ha_csv::external_lock(THD *thd, int lock_type) {
return 0;
}
2
3
返回存储引擎使用的所有可能文件扩展名的数组。
const char ** ha_csv::bas_ext( ) const {
return csv_ext;
}
2
3
我们需要这个函数来报告,在没有WHERE
子句的情况下,不能使用records
成员来优化SELECT COUNT(*)
。需要注意的是,在完整扫描后,records
的值实际上显示的是正确的计数,并且确实可以用于优化SELECT COUNT(*)
。这部分内容留作读者练习。
ulong ha_csv::table_flags(void) const {
return HA_NOT_EXACT_COUNT;
}
2
3
我们的存储引擎不支持键,因此报告没有特殊的键功能。
ulong ha_csv::index_flags(uint idx, uint part, bool all_parts) const {
return 0;
}
2
3
在创建表时,在存储引擎层面无需进行特殊操作。.CSV
文件是在外部放置到数据目录中的。
int ha_csv::create(const char *name, TABLE *form, HA_CREATE_INFO *info) {
return 0;
}
2
3
表锁管理器正常工作需要这个方法。
THR_LOCK_DATA ** ha_csv::store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type) {
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
lock.type=lock_type;
*to++ = &lock;
return to;
}
2
3
4
5
6
7
8
# MySQL 5.1版本的集成说明
以下说明基于MySQL 5.1.11版本编写。虽然在这个版本中,自定义存储引擎的集成过程应该已经趋于稳定,但5.1的后续版本仍有可能引入一些变化,这可能需要对这些说明进行相应调整。如果有疑问,可以在源代码中搜索“blackhole”字符串,并参考黑洞存储引擎的模式。
- 在源代码树中创建一个名为
storage/oreilly-csv
的目录,从本书的网站上把ha_csv_5_1.h
和ha_csv_5_1.cc
文件复制到该目录下,并分别将它们重命名为ha_csv.h
和ha_csv.cc
。如果无法访问互联网,可以使用示例7 - 3和7 - 4中的代码。 - 从本书的网站(或从示例8 - 5)将
Makefile.am
文件复制到storage/oreilly-csv
目录下。 - 在
configure.in
中搜索MYSQL_STORAGE_ENGINE
,找到插件宏的部分。在黑洞插件部分(或其他插件部分)之后添加以下几行:
MYSQL_STORAGE_ENGINE(oreilly-csv,, [《深入理解MySQL内部原理》的示例存储引擎],
[对CSV文件的只读访问])
MYSQL_PLUGIN_DIRECTORY(oreilly-csv, [storage/oreilly-csv])
MYSQL_PLUGIN_STATIC(oreilly-csv, [liboreillycsv.a])
2
3
4
- 执行以下Shell命令:
$ autoconf
$ automake
$ ./configure --prefix=/usr --with-plugins=oreilly-csv
$ make
2
3
4
以下是示例7-3、7-4和7-5。
# 示例7 - 3 MySQL 5.1的ha_csv.h存储引擎头文件
/* 告诉GCC这是一个头文件 */
#ifdef USE_PRAGMA_INTERFACE
#pragma interface
#endif
/* CSV行可能很长,以512字节为块读取 */
#define CSV_READ_BLOCK_SIZE 512
/*
遵循其他存储引擎的惯例,我们将所有底层信息放在一个单独的结构中。
*/
struct CSV_INFO {
char fname[FN_REFLEN+1];
int fd;
};
/* 现在定义handler类 */
class ha_csv: public handler
{
protected:
/* 底层存储引擎数据 */
CSV_INFO* file;
/* 用于表锁管理器的锁结构 */
THR_LOCK_DATA lock;
THR_LOCK thr_lock;
/* 表扫描游标 */
my_off_t pos;
/* 用于读取CSV行块的缓冲区 */
char read_buf[CSV_READ_BLOCK_SIZE];
/* 用于解析字段值的缓冲区 */
String field_buf;
/* 见实现文件中的注释 */
int fetch_line(byte* buf);
/* 初始化用于顺序扫描的存储引擎对象 */
int rnd_init(bool scan)
{
pos = 0;
records = 0;
return 0;
}
int index_init(uint idx)
{
active_index=idx;
return 0;
}
public:
/* 构造函数 */
ha_csv(TABLE_SHARE* table_arg);
/* 析构函数 */
~ha_csv( ) {}
/* 见下面实现文件中关于这些方法的注释 */
int open(const char *name, int mode, uint test_if_locked);
int close(void);
int rnd_next(byte *buf);
int rnd_pos(byte * buf, byte *pos);
void position(const byte *record);
void info(uint flags);
int external_lock(THD *thd, int lock_type);
const char **bas_ext( ) const;
ulong table_flags(void) const;
ulong index_flags(uint idx, uint part, bool all_parts) const;
int create(const char *name, TABLE *form, HA_CREATE_INFO *info);
THR_LOCK_DATA **store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type);
/*
返回用于SHOW TABLE STATUS输出中的存储引擎类型字符串
*/
const char *table_type( ) const { return "OREILLY_CSV"; }
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 示例7 - 4 MySQL 5.1的ha_csv.cc存储引擎实现文件
/* 告诉GCC我们在实现源文件中 */
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation
#endif
#include "mysql_priv.h"
#include <mysql/plugin.h>
/* 我们这个存储引擎自己的头文件 */
#include "ha_csv.h"
/* 供ha_csv::bas_ext()使用 */
static const char* csv_ext[]= {".csv",0};
/* 用于handlerton描述符的回调函数 */
static handler *csv_create_handler(TABLE_SHARE *table);
/*
在插件以及handlerton描述符中使用。对应ENGINE=语法中使用的存储引擎名称
*/
static const char csv_hton_name[]= "OREILLY_CSV";
/*
在插件描述符中使用。对应SHOW STORAGE ENGINES输出中显示的注释
*/
static const char csv_hton_comment[]=
"Simple read-only CSV file storage engine";
handlerton csv_hton= {
MYSQL_HANDLERTON_INTERFACE_VERSION,
csv_hton_name,
SHOW_OPTION_YES,
csv_hton_comment,
DB_TYPE_BLACKHOLE_DB,
NULL,
0, /* 插槽 */
0, /* 保存点大小 */
NULL, /* 关闭连接 */
NULL, /* 保存点 */
NULL, /* 回滚到保存点 */
NULL, /* 释放保存点 */
NULL, /* 提交 */
NULL, /* 回滚 */
NULL, /* 准备 */
NULL, /* 恢复 */
NULL, /* 通过XID提交 */
NULL, /* 通过XID回滚 */
NULL, /* 创建游标读视图 */
NULL, /* 设置游标读视图 */
NULL, /* 关闭游标读视图 */
csv_create_handler, /* 创建一个新的handler */
NULL, /* 删除数据库 */
NULL, /* 紧急调用 */
NULL, /* 启动一致性快照 */
NULL, /* 刷新日志 */
NULL, /* 显示状态 */
NULL, /* 分区标志 */
NULL, /* 更改表标志 */
NULL, /* 更改表空间 */
NULL, /* 填充FILES表 */
HTON_CAN_RECREATE | HTON_ALTER_CANNOT_CREATE,
NULL, /* binlog函数 */
NULL, /* 记录binlog查询 */
NULL /* 释放临时锁 */
};
/*
用于实例化handler对象的回调包装函数
*/
static handler *csv_create_handler(TABLE_SHARE *table) {
return new ha_csv(table);
}
/* 构造函数 */
ha_csv::ha_csv(TABLE_SHARE *table_arg) :handler(&csv_hton, table_arg)
{}
/* 当表打开时调用 */
int ha_csv::open(const char *name, int mode, uint test_if_locked) {
/* 初始化锁管理器使用的锁结构 */
thr_lock_init(&thr_lock);
thr_lock_data_init(&thr_lock,&lock,NULL);
/* 为数据文件描述符分配内存 */
file= (CSV_INFO*)my_malloc(sizeof(CSV_INFO),MYF(MY_WME));
if (!file)
return 1;
/* 将表名转换为数据文件名 */
fn_format(file->fname, name, "", ".csv",
MY_REPLACE_EXT|MY_UNPACK_FILENAME);
/*
打开文件,并将文件句柄ID保存在数据文件描述符结构中
*/
if ((file->fd = my_open(file->fname,mode,MYF(0))) < 0) {
int error = my_errno;
close( );
return error;
}
/* 读取操作从文件开头开始 */
pos = 0;
return 0;
}
/* 当表关闭时调用 */
int ha_csv::close(void)
{
/*
清理锁结构,关闭文件句柄,并释放数据文件描述符的内存
*/
thr_lock_delete(&thr_lock);
if (file)
{
if (file->fd >= 0)
my_close(file->fd, MYF(0));
my_free((gptr)file,MYF(0));
file = 0;
}
return 0;
}
/*
从当前位置读取一行数据到调用者提供的记录缓冲区中
*/
int ha_csv::fetch_line(byte* buf) {
/*
读取数据行的部分内容到缓冲区时,跟踪文件中的当前偏移量。从当前读取游标位置开始。
*/
my_off_t cur_pos = pos;
/*
我们将使用这个遍历表字段指针数组,以便将解析后的数据以正确的格式存储到正确的位置。
*/
Field** field = table->field;
/*
在解析时用于记住前一个字符。256这个不可能的值表示最后一个字符要么不存在(我们在第一个字符处),要么其值无关紧要。
*/
int last_c = 256;
/* 如果在引号字符串内则设置为1 */
int in_quote = 0;
/* 到目前为止,在这一行中我们已经看到了多少字节 */
uint bytes_parsed = 0;
/* 循环中断标志 */
int line_read_done = 0;
/* 截断字段值缓冲区 */
field_buf.length(0);
/* 尝试读取一整行 */
for (;!line_read_done;)
{
/* 读取一个块到本地缓冲区并处理错误 */
char buf[CSV_READ_BLOCK_SIZE];
uint bytes_read = my_pread(file->fd,buf,sizeof(buf),cur_pos,MYF(MY_WME));
if (bytes_read == MY_FILE_ERROR)
return HA_ERR_END_OF_FILE;
if (!bytes_read)
return HA_ERR_END_OF_FILE;
/*
如果执行到这里,说明读取成功。开始解析我们读取的数据。
*/
char* p = buf;
char* buf_end = buf + bytes_read;
/* 对于缓冲区中的每个字节 */
for (;p < buf_end;)
{
char c = *p;
int end_of_line = 0;
int end_of_field = 0;
int char_escaped = 0;
switch (c) {
/*
双引号标记着引号字符串的开始或结束,除非它被转义。
*/
case '"':
if (last_c == '"' || last_c == '\\') {
field_buf.append(c);
char_escaped = 1;
/*
当我们看到第一个引号时,in_quote会翻转。然而,后续的引号表示我们仍在引号字符串内。
*/
if (last_c == '"')
in_quote = 1;
}
else
in_quote = !in_quote;
break;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 示例7 - 4 MySQL 5.1的ha_csv.cc存储引擎实现文件(续)
/* 把反斜杠当作转义字符 */
case '\\':
if (last_c == '\\') {
field_buf.append(c);
char_escaped = 1;
}
break;
/*
除非是在引号内,否则遇到换行符就设置终止标志。
*/
case '\r':
case '\n':
if (in_quote) {
field_buf.append(c);
}
else {
end_of_line = 1;
end_of_field = 1;
}
break;
/* 除非在引号内,逗号表示字段结束 */
case ',':
if (in_quote) {
field_buf.append(c);
}
else
end_of_field = 1;
break;
/* 普通字符直接追加到字段值缓冲区 */
default:
field_buf.append(c);
break;
}
/*
如果到达字段末尾,并且表中存在匹配的字段(如果CSV文件有额外的字段,可能不存在匹配字段),将字段值缓冲区的内容转移到相应的Field对象中。这实际上负责初始化调用者传递给我们的缓冲区参数的正确部分。优化器的内部约定要求在调用handler类的数据检索方法之前,Field对象的缓冲区指针必须已经设置为指向缓冲区参数的正确区域。
*/
if (end_of_field && *field) {
(*field)->store(field_buf.ptr(), field_buf.length(),
system_charset_info);
field++;
field_buf.length(0);
}
/*
特殊情况 - 本身被转义的字符不应被视为转义字符。
*/
if (char_escaped)
last_c = 256;
else
last_c = c;
p++;
/* 准备在行尾退出循环 */
if (end_of_line) {
if (c == '\r')
p++;
line_read_done = 1;
in_quote = 0;
break;
}
}
/* 块读取/解析循环完成 - 更新计数器 */
bytes_parsed += (p - buf);
cur_pos += bytes_read;
}
/*
现在我们完成了行读取/解析。我们还有一些小任务需要完成。
*/
/* 初始化记录中的NULL指示标志 */
memset(buf, 0, table->s->null_bytes);
/*
解析后的行可能没有包含所有字段的值。将剩余字段设置为默认值。
*/
for (; *field; field++) {
(*field)->set_default();
}
/* 将游标移动到下一条记录 */
pos += bytes_parsed;
/* 报告成功 */
return 0;
}
/* 在顺序表扫描期间,为每条记录调用一次 */
int ha_csv::rnd_next(byte *buf)
{
/*
增加SHOW STATUS中Handler_read_rnd_next下显示的全局统计计数器。
*/
ha_statistic_increment(&SSV::ha_read_rnd_next_count);
/* fetch_line()负责实际工作 */
int error = fetch_line(buf);
/*
如果成功,更新我们对表中记录总数的估计。
*/
if (!error)
records++;
/* 将从fetch_line()得到的代码返回给调用者 */
return error;
}
/*
将扫描游标定位到set_pos指定的位置,并读取该位置的记录。在应用“filesort”技术进行GROUP BY和ORDER BY优化时使用。
*/
int ha_csv::rnd_pos(byte *buf, byte *set_pos) {
ha_statistic_increment(&SSV::ha_read_rnd_count);
pos = my_get_ptr(set_pos, ref_length);
return fetch_line(buf);
}
/*
将当前记录的“位置”引用存储到ref变量中。目前,这个方法在这个存储引擎中被调用的情况是不可能出现的,但未来可能会改变。
*/
void ha_csv::position(const byte *record) {
my_store_ptr(ref, ref_length, pos);
}
/* 更新handler对象中的统计变量 */
void ha_csv::info(uint flags)
{
/*
优化器绝不能认为表中的记录少于两条,除非确实如此。报告较少的记录数会使优化器认为它只需要从表中读取一条记录。我们的存储引擎并不总是知道记录的数量,在很多情况下甚至无法做出合理的猜测。为了安全和简化操作,我们总是报告至少有两条记录。
*/
if (records < 2)
records = 2;
/*
其余变量仅出现在SHOW TABLE STATUS输出中,不会影响优化器。对于这个示例,它们可以设置为0。
*/
deleted = 0;
errkey = 0;
mean_rec_length = 0;
data_file_length = 0;
index_file_length = 0;
max_data_file_length = 0;
delete_length = 0;
if (flags & HA_STATUS_AUTO)
auto_increment_value = 1;
}
/*
这本质上是表锁管理器的一个回调函数,意思是:“我正在内部锁定这个表;请处理与这个锁相关的、特定于存储引擎的事情。”在某些配置中,MyISAM在这种情况下需要锁定文件,因此这个函数名为external_lock()。在我们的例子中,没有什么需要做的——我们只报告成功。
*/
int ha_csv::external_lock(THD *thd, int lock_type) {
return 0;
}
/*
返回存储引擎使用的所有可能文件扩展名的数组。
*/
const char **ha_csv::bas_ext() const {
return csv_ext;
}
/*
我们需要这个函数来报告,在没有WHERE子句的情况下,records成员不能用于优化SELECT COUNT(*)。注意,在完整扫描之后,records的值实际上显示的是正确的计数,并且确实可以用于优化SELECT COUNT(*)。这留给读者作为练习。
*/
ulong ha_csv::table_flags(void) const {
return HA_NOT_EXACT_COUNT;
}
/*
我们的存储引擎不支持键,所以我们报告没有特殊的键功能。
*/
ulong ha_csv::index_flags(uint idx, uint part, bool all_parts) const {
return 0;
}
/*
当创建表时,在存储引擎层面没有什么特殊的事情要做。CSV文件是在外部放置到数据目录中的。
*/
int ha_csv::create(const char *name, TABLE *form, HA_CREATE_INFO *info) {
return 0;
}
/* 这个方法是表锁管理器正常工作所必需的。 */
THR_LOCK_DATA **ha_csv::store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type) {
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
lock.type = lock_type;
*to++ = &lock;
return to;
}
/* 定义插件的全局结构。 */
mysql_declare_plugin(oreilly_csv)
{
MYSQL_STORAGE_ENGINE_PLUGIN,
&csv_hton,
csv_hton_name,
"Sasha Pachev",
csv_hton_comment,
NULL, /* 插件初始化函数 */
NULL, /* 插件结束函数 */
0x0100,
0
};
mysql_declare_plugin_end;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# 示例7 - 5 MySQL 5.1版本的Makefile.am
MYSQLDATAdir = $(localstatedir)
MYSQLSHAREdir = $(pkgdatadir)
MYSQLBASEdir = $(prefix)
MYSQLLIBdir = $(pkglibdir)
INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \
-I$(top_srcdir)/regex \ -I$(top_srcdir)/sql \
-I$(srcdir)
WRAPLIBS =
LDADD =
DEFS = @DEFS@
noinst_HEADERS = ha_csv.h
EXTRA_LIBRARIES = liboreillycsv.a
noinst_LIBRARIES = @plugin_oreilly_csv_static_target@
liboreillycsv_a_CXXFLAGS =
liboreillycsv_a_CFLAGS =
liboreillycsv_a_SOURCES = $(AM_CFLAGS) $(AM_CFLAGS) ha_csv.cc
# 不要从BitKeeper更新文件
%::SCCS/s.%
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
部署新二进制文件有多种方法。如果你打算对其进行扩展,请回顾第3章中关于如何编写测试用例并在调试器中执行的信息。如果你只是想运行二进制文件并查看结果,假设你已经安装了与源代码版本相同的常规MySQL,那么你可以备份常规的mysqld
二进制文件,用新构建的二进制文件替换它,然后重启服务器。
要测试新的存储引擎是否可用,需创建一个以逗号分隔的文件,其基本名称与表名匹配,扩展名为.csv(例如,t1.csv),并将其放置在与你计划使用的数据库对应的目录中。例如,如果你的数据目录(datadir)设置为/var/lib/mysql,且你想在test数据库中创建表,那么就将该文件放置在/var/lib/mysql/test目录下。创建好文件后,在相应数据库中创建一个类型为OREILLY_CSV的表,表中的字段要与文件中的字段对应。此时,你就可以对该表运行SELECT查询了。
前面较长示例中的注释对代码的细节进行了详细解释。这里,我再补充一些关于扩展该引擎时需要注意的问题。
为简化示例,我们设计的存储引擎是只读的,不支持更新或删除操作。若要添加写入功能,我们需要解决同一表存在多个处理程序对象实例的问题。在我们的示例中,除了浪费文件描述符外,这不会带来太大问题。写入操作需要我们跟踪当前的写入位置,如果使用另一个处理程序对象实例执行写入操作,可能会引发问题。其他存储引擎通过维护共享的底层表描述符结构缓存来解决这个问题,这样做还有一个好处,就是可以减少文件描述符的使用。若想了解具有写入功能的CSV存储引擎示例,可以查看sql/examples/ha_tina.h和sql/examples/ha_tina.cc。
我们的存储引擎不支持键。如果你想添加键支持,强烈建议先实现写入功能。之后,你就可以着手创建自己的B树、哈希表以及其他形式的索引,这会很有挑战性和乐趣。
我们的引擎通过调用my_pread()从文件描述符读取数据块,然后进行解析。这种方式虽然在处理I/O错误时更稳健,但不如使用mmap()高效和便捷。sql/examples/ha_tina.cc中的示例使用了mmap()。InnoDB使用常规文件I/O。MyISAM对于压缩表使用mmap()。在5.1版本中,MyISAM也可以选择对常规表使用mmap()。