目录
对于MySQL 5.1ï¼�MySQL AB公司引入了插件式存储引擎体系结构,这样,就能创建新的存储引擎,并将它们添加到正在运行çš�MySQL服务器上,而不必重新编译服务器本身ã€�
该体系结构简化了新存储引擎的开发和部署ã€�
本章的意图是作为指南,用于帮助你为新的插件式存储引擎体系结构开发存储引擎ã€�
关于MySQL插件式存储引擎体系结构的更多信息,请参见ç¬�14章:插件式存储引擎体系结æž�ã€�每个存储引擎均是1个继承类,每个类实例作为处理程序而被引用ã€�
针对需要与特殊表一起工作的每个线程,处理程序是åœ�1个处理程序的基础上实例化的。例如,如果3个连接全都在相同的表上工作,需要创å»�3个处理程序实例ã€�
一旦创建了处理程序实例ï¼�MySQL服务器将向处理程序发送命令,以便执行数据存储和检索任务,如打开表、操纵行和管理索引等ã€�
能够以累进方式创建定制存储引擎:开发人员能够以只读存储引擎启动,随后添加对INSERTã€�UPDATEå’�DELETE操作的支持,甚至能够增加对索引功能、事务和其他高级操作的支持ã€�
实施新存储引擎的最简单方法是,通过拷贝和更æ”�EXAMPLE存储引擎开始。在MySQL 5.1源码树的sql/examples/目录下可找到文件ha_example.ccå’�ha_example.h。关于如何获å¾�5.1源码树的说明,请参见2.8.3节,“从开发源码树安装â€�ã€�
复制文件时,将名称从ha_example.ccå’�ha_example.h更改为与存储引擎相适应的名称,å¦�ha_foo.ccå’�ha_foo.hã€�
拷贝并重命名了这些文件后,必须更换所有的EXAMPLE示例,以及具有存储引擎名称的示例。如果你熟悉sed,也能自动完成这些步骤:
sed s/EXAMPLE/FOO/g ha_example.h | sed s/example/foo/g ha_foo.h
sed s/EXAMPLE/FOO/g ha_example.cc | sed s/example/foo/g ha_foo.cc
handlerton(“单个处理程序”的简ç§�ï¼�定义了存储引擎,并包含指向函数的函数指针,它以整体方式作用在引擎上,而函数工作在单独的处理程序实例中。在这类函数的一些示例中,包含用于处理注释和回滚的事务函数ã€�
下面给出了一个来è‡�EXAMPLE存储引擎的示例:
handlerton example_hton= {
"EXAMPLE",
SHOW_OPTION_YES,
"Example storage engine",
DB_TYPE_EXAMPLE_DB,
NULL, /* Initialize */
0, /* slot */
0, /* savepoint size. */
NULL, /* close_connection */
NULL, /* savepoint */
NULL, /* rollback to savepoint */
NULL, /* release savepoint */
NULL, /* commit */
NULL, /* rollback */
NULL, /* prepare */
NULL, /* recover */
NULL, /* commit_by_xid */
NULL, /* rollback_by_xid */
NULL, /* create_cursor_read_view */
NULL, /* set_cursor_read_view */
NULL, /* close_cursor_read_view */
example_create_handler, /* Create a new handler */
NULL, /* Drop a database */
NULL, /* Panic call */
NULL, /* Release temporary latches */
NULL, /* Update Statistics */
NULL, /* Start Consistent Snapshot */
NULL, /* Flush logs */
NULL, /* Show status */
NULL, /* Replication Report Sent Binlog */
HTON_CAN_RECREATE
};
下面给出了来è‡�handler.hçš�handlerton定义ï¼�
typedef struct
{
const char *name;
SHOW_COMP_OPTION state;
const char *comment;
enum db_type db_type;
bool (*init)();
uint slot;
uint savepoint_offset;
int (*close_connection)(THD *thd);
int (*savepoint_set)(THD *thd, void *sv);
int (*savepoint_rollback)(THD *thd, void *sv);
int (*savepoint_release)(THD *thd, void *sv);
int (*commit)(THD *thd, bool all);
int (*rollback)(THD *thd, bool all);
int (*prepare)(THD *thd, bool all);
int (*recover)(XID *xid_list, uint len);
int (*commit_by_xid)(XID *xid);
int (*rollback_by_xid)(XID *xid);
void *(*create_cursor_read_view)();
void (*set_cursor_read_view)(void *);
void (*close_cursor_read_view)(void *);
handler *(*create)(TABLE *table);
void (*drop_database)(char* path);
int (*panic)(enum ha_panic_function flag);
int (*release_temporary_latches)(THD *thd);
int (*update_statistics)();
int (*start_consistent_snapshot)(THD *thd);
bool (*flush_logs)();
bool (*show_status)(THD *thd, stat_print_fn *print, enum ha_stat_type stat);
int (*repl_report_sent_binlog)(THD *thd, char *log_file_name, my_off_t end_offset);
uint32 flags;
} handlerton;
共有30ä¸�handlerton元素,但只有少量元素是强制性的(明确地讲是å‰�4个元素和ç¬�21个元素)ã€�
1. 存储引擎的名称。这是创建表时将使用的名称(CREATE TABLE ... ENGINE = FOO;)ã€�
2. 确定使用SHOW STORAGE ENGINES命令时是否列出存储引擎ã€�
3. 存储引擎注释,对使用SHOW STORAGE ENGINES命令时显示的存储引擎的描述ã€�
4. åœ�MySQL服务器内唯一识别存储引擎的整数。内置存储引擎使用的常数定义åœ�handler.h文件中。作为创建常数的可选方法,可使用大äº�25的整数ã€�
5. 指向存储引擎初始化程序的指针。仅当启动服务器时才调用该函数,以便在实例化处理程序之前,存储引擎类能执行必要的内务操作ã€�
6. 插槽。保存每连接的信息时,每个存储引擎在thd中有自己的内存区域(实际上为指针)。它是作ä¸�thd->ha_data[foo_hton.slot]访问的ã€�插槽编号在调ç”�foo_init()å��ç”�MySQL初始化ã€�
7. 保存点偏移。为了保存每ä¸�savepoint数据,为存储引擎提供了请求的大小(典型情况下ä¸�0)ã€�
必须以静态方式初始化savepoint偏移,使其具有所有的内存大小,以便保存每ä¸�savepoint的信息。在foo_init之后,它被更改为savepoint存储区域的偏移,存储引擎不需要使用它ã€�
8. 由事务性存储引擎使用,清理其存储段内分配的内存,和/或回滚任何未完成的事务ã€�
9. 由事务性存储引擎选择性使用,创建savepoint(保存点),并将其保存到提供的内存中ã€�
10.指向处理程序rollback_to_savepoint()函数的函数指针。它用于在事务期间返å›�savepoint。仅对支持保存点的存储引擎才会填充它ã€�
11.指向处理程序release_savepoint()函数的函数指针。它用于在事务期é—�释放保存点的资源。仅对支持保存点的存储引擎才会填充它ã€�
12.指向处理程序commit()函数的函数指针ã€�它用于提交事务。仅对支持事务的存储引擎才会填充它ã€�
13.指向处理程序rollback()函数的函数指针ã€�它用于回滚交易。仅对支持事务的存储引擎才会填充它ã€�
14.XA事务性存储引擎所需。为提交操作准备事务。将XID与事务关联起来ã€�
15.XA事务性存储引擎所需。恢复由XID标识的事务ã€�
16.XA事务性存储引擎所需。提交由XID标识的事务ã€�
17.XA事务性存储引擎所需。回滚由XID标识的事务ã€�
18.与服务器端光标一起使用,尚未实施ã€�
19.与服务器端光标一起使用,尚未实施ã€�
20.与服务器端光标一起使用,尚未实施ã€�
21.MANDATORY:构造并返回处理程序实例ã€�
22.撤销方案时,如果存储引擎需要执行特殊步骤时使用(如在使用表空间的存储引擎中使用)ã€�
23.清理在服务器关闭和崩溃时调用的函数ã€�
24.InnoDB特殊函数ã€�
25.在启åŠ�SHOW STATUS时调ç”�InnoDB特殊函数ã€�
26.调用InnoDB特殊函数以开始连续读取ã€�
27.调用它,指明应将日志刷新为可靠的存储ã€�
28.在存储引擎上提供可被人员读取的状态信息ã€�
29.InnoDB特殊函数用于复制ã€�
30.Handlerton标志,通常ä¸�ALTER TABLE相关。可能的值定义于sql/handler.h文件中,并在此列出;
31. #define HTON_NO_FLAGS 0
32. #define HTON_CLOSE_CURSORS_AT_COMMIT (1 << 0)
33. #define HTON_ALTER_NOT_SUPPORTED (1 << 1)
34. #define HTON_CAN_RECREATE (1 << 2)
35. #define HTON_FLUSH_AFTER_RENAME (1 << 3)
36. #define HTON_NOT_USER_SELECTABLE (1 << 4)
HTON_ALTER_NOT_SUPPORTEDç”�FEDERATED存储引擎使用,用以指明存储引擎不接受AFTER TABLE语句ã€�
HTON_FLUSH_AFTER_RENAME指明,重命名表后 ,必须调ç”�FLUSH LOGSã€�
HTON_NOT_USER_SELECTABLE指明存储引擎不能由用户选择,而是用作系统存储引擎,如用于二进制日志的伪存储引擎ã€�
调用存储引擎的第1个方法是调用新的处理程序实例ã€�
在存储引擎源文件中定ä¹�handlerton之前,必须定义用于函数实例化的函数题头ã€�下面给出äº�1个来è‡�CSV引擎的示例:
static handler* tina_create_handler(TABLE *table);
正如你所见到的那样,函数接受指向处理程序准备管理的表的指针,并返回处理程序对象ã€�
定义了函数题头后,用ç¬�21ä¸�handlerton元素中的函数指针命名函数ï¼�指明函数负责生成新的处理程序实例ã€�
下面给出äº�MyISAM存储引擎的实例化函数示例ï¼�
static handler *myisam_create_handler(TABLE *table)
{
return new ha_myisam(table);
}
该调用随后与存储引擎的构造程序一起工作。下面给出了来自FEDERATED存储引擎çš�1个示例:
ha_federated::ha_federated(TABLE *table_arg)
:handler(&federated_hton, table_arg),
mysql(0), stored_result(0), scan_flag(0),
ref_length(sizeof(MYSQL_ROW_OFFSET)), current_position(0)
{}
下面给出了来è‡�EXAMPLE存储引擎的另一个示例:
ha_example::ha_example(TABLE *table_arg)
:handler(&example_hton, table_arg)
{}
FEDERATED示例中的附加元素æ˜�处理程序的额外初始化要素。所要求的最低实施是EXAMPLE示例中显示的handler()初始化ã€�
就给定的表、数据和索引,要求存储引擎为MySQL服务器提供存储引擎所使用的扩展列表ã€�
扩展应采用以Null终结的字符串数组形式。下面给出了CSV引擎使用的数组:
static const char *ha_tina_exts[] = {
".CSV",
NullS
};
调用bas_ext()函数时返回该数组ã€�
const char **ha_tina::bas_ext() const
{
return ha_tina_exts;
}
通过提供扩展信息,你还能忽略DROP TABLE功能的实施,这是因为,通过关闭表并用你指定的扩展删除所有文件,MySQL服务器能实现该功能ã€�
一旦实例化了处理程序,所需的第1个操作很可能是创建表ã€�
你的存储引擎必须实现create()虚拟函数ï¼�
virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *info)=0;
该函数应创建所有必须的文件,然后关闭表ã€�MySQL服务器将调用随后需打开的表ã€�
*name参数是表的名称ã€�*form参数是st_table结构,该结构定义了表并与MySQL服务器已创建çš�tablename.frm文件的内容匹配。在大多数情况下ï¼�存储引擎不需要更æ”�tablename.frm文件,也没有支持该操作的预置功能ã€�
*info参数是包å��CREATE TABLE语句用于创建表所需信息的结构。该结构定义äº�handler.h文件中,并为了便于参考列于下面:
typedef struct st_ha_create_information
{
CHARSET_INFO *table_charset, *default_table_charset;
LEX_STRING connect_string;
const char *comment,*password;
const char *data_file_name, *index_file_name;
const char *alias;
ulonglong max_rows,min_rows;
ulonglong auto_increment_value;
ulong table_options;
ulong avg_row_length;
ulong raid_chunksize;
ulong used_fields;
SQL_LIST merge_list;
enum db_type db_type;
enum row_type row_type;
uint null_bits; /* NULL bits at start of record */
uint options; /* OR of HA_CREATE_ options */
uint raid_type,raid_chunks;
uint merge_insert_method;
uint extra_size; /* length of extra data segment */
bool table_existed; /* 1 in create if table existed */
bool frm_only; /* 1 if no ha_create_table() */
bool varchar; /* 1 if table has a VARCHAR */
} HA_CREATE_INFO;
基本的存储引擎能忽略*formå’�*info的内容,这是因为,真正所需的是创建存储引擎所使用的数据文件,以及对数据文件的可能初始化操作(假定存储文件是基于文件的)ã€�
下面给出了来è‡�CSV存储引擎的实施示例:
int ha_tina::create(const char *name, TABLE *table_arg,
HA_CREATE_INFO *create_info)
{
char name_buff[FN_REFLEN];
File create_file;
DBUG_ENTER("ha_tina::create");
if ((create_file= my_create(fn_format(name_buff, name, "", ".CSV",
MY_REPLACE_EXT|MY_UNPACK_FILENAME),0,
O_RDWR | O_TRUNC,MYF(MY_WME))) < 0)
DBUG_RETURN(-1);
my_close(create_file,MYF(0));
DBUG_RETURN(0);
}
在前面的例子中,CSV引擎未引ç”�*table_argæˆ�*create_info参数,而是简单地创建了所需的数据文件,关闭它们,并返回ã€�
my_createå’�my_close函数是定义于src/include/my_sys.h文件中的助手函数ã€�
在表上执行任何读或写操作之前ï¼�MySQL服务器将调用open()方法打开表数据和索引文件(如果存在的话)ã€�
int open(const char *name, int mode, int test_if_locked);
ç¬�1个参数是要打开的表的名称。第2个参数确定了要打开的文件或准备执行的操作。它们的值定义于handler.h中,并为了方便起见列在下é��ï¼�
#define HA_OPEN_KEYFILE 1
#define HA_OPEN_RNDFILE 2
#define HA_GET_INDEX 4
#define HA_GET_INFO 8 /* do a ha_info() after open */
#define HA_READ_ONLY 16 /* File opened as readonly */
#define HA_TRY_READ_ONLY 32 /* Try readonly if can't open with read and write */
#define HA_WAIT_IF_LOCKED 64 /* Wait if locked on open */
#define HA_ABORT_IF_LOCKED 128 /* skip if locked on open.*/
#define HA_BLOCK_LOCK 256 /* unlock when reading some records */
#define HA_OPEN_TEMPORARY 512
最后一个选项规定了是否要在打开表之前检查表上的锁定ã€�
在典型情况下,存储引擎需要实施某种形式的共享访问控制,以防止在多线程环境下的文件损坏。关于如何实施文件锁定的示例,请参见sql/examples/ha_tina.ccçš�get_share()å’�free_share()方法ã€�
最基本的存储引擎能实现只读表扫描功能。这类引擎可用于支持SQL日志查询、以及在MySQL之外填充的其他数据文件ã€�
本节介绍的方法实施提供了创建更高级存储引擎的基础ã€�
下面给出了在CSV引擎çš�9行表扫描过程中进行的方法调用ï¼�
ha_tina::store_lock ha_tina::external_lock ha_tina::info ha_tina::rnd_init ha_tina::extra - ENUM HA_EXTRA_CACHE Cache record in HA_rrnd() ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::rnd_next ha_tina::extra - ENUM HA_EXTRA_NO_CACHE End cacheing of records (def) ha_tina::external_lock ha_tina::extra - ENUM HA_EXTRA_RESET Reset database to after open
在执行任何读取或写操作之前,调用store_lock()函数ã€�
将锁定添加到表锁定处理程序之前(请参è§�thr_lock.c),mysqld将用请求的锁调用存储锁定。目前,存储锁定能将写锁定更改为读锁定(或其他锁定),忽略锁定(如果不打算使ç”�MySQL锁定的话),或为很多表添加锁定(就像使用MERGE处理程序时作的那样)ã€�
例如ï¼�Berkeley DB能将所有的WRITE锁定更改ä¸�TL_WRITE_ALLOW_WRITE(表示我们正在执è¡�WRITES,但我们仍允许其他人员进行操作)ã€�
释放锁定时,也将调用store_lock(),在这种情况下,通常不需做任何事ã€�
在某些特殊情况下ï¼�MySQL可能会发送对TL_IGNORE的请求。这意味着我们正在请求与上次相同的锁定,这也应被忽略(当我们打开了表的某一部分时,如果其他人执行了表刷新操作,就会出现该情况,此时ï¼�mysqld将关闭并再次打开表,然后获取与上次相同的锁定)。我们打算在将来删除该特性ã€�
可能的锁定类型定义于includes/thr_lock.h中,并列在下面:
enum thr_lock_type
{
TL_IGNORE=-1,
TL_UNLOCK, /* UNLOCK ANY LOCK */
TL_READ, /* Read lock */
TL_READ_WITH_SHARED_LOCKS,
TL_READ_HIGH_PRIORITY, /* High prior. than TL_WRITE. Allow concurrent insert */
TL_READ_NO_INSERT, /* READ, Don't allow concurrent insert */
TL_WRITE_ALLOW_WRITE, /* Write lock, but allow other threads to read / write. */
TL_WRITE_ALLOW_READ, /* Write lock, but allow other threads to read / write. */
TL_WRITE_CONCURRENT_INSERT, /* WRITE lock used by concurrent insert. */
TL_WRITE_DELAYED, /* Write used by INSERT DELAYED. Allows READ locks */
TL_WRITE_LOW_PRIORITY, /* WRITE lock that has lower priority than TL_READ */
TL_WRITE, /* Normal WRITE lock */
TL_WRITE_ONLY /* Abort new lock request with an error */
};
实际的锁定处理因锁定实施的不同而不同,你可以选择某些请求的锁定类型或不选择任何锁定类型,并根据情况恰当地代入你自己的方法。下面给出了1ä¸�CSV存储引擎实施示例ï¼�
THR_LOCK_DATA **ha_tina::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;
}
external_lock()函数是在事务开始时调用的,或发å‡�LOCK TABLES语句时调用的,用于事务性存储引擎ã€�
åœ�sql/ha_innodb.ccå’�sql/ha_berkeley.cc文件中,可找åˆ�使用external_lock()的示例,但大多数存储引擎简单地返回0ï¼�就像EXAMPLE存储引擎那样ï¼�
int ha_example::external_lock(THD *thd, int lock_type)
{
DBUG_ENTER("ha_example::external_lock");
DBUG_RETURN(0);
}
在任何表扫描之前调用的函数是rnd_init()函数。函æ•�rnd_init()用于为表扫描作准备,将计数器和指针复位为表的开始状态ã€�
下述示例来自CSV存储引擎ï¼�
int ha_tina::rnd_init(bool scan)
{
DBUG_ENTER("ha_tina::rnd_init");
current_position= next_position= 0;
records= 0;
chain_ptr= chain;
DBUG_RETURN(0);
}
优化程序所需的信息不是通过返回值给定的,你需填充存储引擎类的特定属性,å½�info()调用返回后,优化程序将读取存储引擎类ã€�
除了供优化程序使用外,在调用info()函数期间,很多值集合还将用äº�SHOW TABLE STATUS语句ã€�
åœ�sql/handler.h中列出了完整的公共属性,下面给出了一些常见的属性:
ulonglong data_file_length; /* Length off data file */
ulonglong max_data_file_length; /* Length off data file */
ulonglong index_file_length;
ulonglong max_index_file_length;
ulonglong delete_length; /* Free bytes */
ulonglong auto_increment_value;
ha_rows records; /* Records in table */
ha_rows deleted; /* Deleted records */
ulong raid_chunksize;
ulong mean_rec_length; /* physical reclength */
time_t create_time; /* When table was created */
time_t check_time;
time_t update_time;
对于表扫描,最重要的属性是â€�recordsâ€�,它指明了表中的记录数。当存储引擎指明表中æœ�0æˆ�1行时,或æœ�2行以上时,在这两种情况下,优化程序的执行方式不同。因此,当你在执行表扫描之前不清楚表中有多少行时,应返回大于等于2的值,这很重要(例如,数据是在外部填充的)ã€�
执行某些操作之前,应调用extra()函数,以便为存储引擎就如何执行特定操作予以提示ã€�
额外调用中的提示实施不是强制性的,大多数存储引擎均返å›�0ï¼�
int ha_tina::extra(enum ha_extra_function operation)
{
DBUG_ENTER("ha_tina::extra");
DBUG_RETURN(0);
}
完成表的初始化操作后ï¼�MySQL服务器将调用处理程序çš�rnd_next()函数,每两个扫描行调ç”�1次,直至满足了服务器的搜索条件或到达文件结尾为止,在后一种情况下,处理程序将返回HA_ERR_END_OF_FILEã€�
rnd_next()函数有一个名ä¸�*buf的单字节数组参数。对äº�*buf参数,必须按内部MySQL格式用表行的内容填充它ã€�
服务器采用了三种数据格式:固定长度行,可变长度行,以及具æœ�BLOB指针的可变长度行。对于每种格式,各列将按照它们由CREATE TABLE语句定义的顺序显示(表定义保存在.frm文件中,优化程序和处理程序均能从相同的源,即TABLE结构,访问表的元数据)ã€�
每种格式以每åˆ�1比特çš�"NULL bitmap"开始。对于含6个列的表,其bitmapä¸�1字节,对于含9ï½�16列的表,å…�bitmapä¸�2字节,依此类推。要想指明特定的值是NULL,应将该åˆ�NULL位设置为1ã€�
å½�NULL bitmap逐个进入列后,每列将具有MySQL手册的â€�MySQL数据类型”一节中指定的大小。在服务器中,列的数据类型定义在sql/field.cc文件中。对于固定长度行格式,列将简单地逐个放置。对于可变长度行ï¼�VARCHAR列将被编码为1字节长,后跟字符串。对于具æœ�BLOB列的可变长度行,每个blob由两部分表示:首先是表示BLOB实际大小的整数,然后是指向内存中BLOB的指针ã€�
在任何表处理程序中从rnd_next()开始,可找åˆ�行转换(或“包装”)的示例。例如,åœ�ha_tina.cc中,find_current_row()内的代码给出了使ç”�TABLE结构(由表指向的)和字符串对象(命名缓冲)包装字符数据(来自CSV文件)的方法。将行写回磁盘需要反向转换,从内部格式解包ã€�
下述示例来自CSV存储引擎ï¼�
int ha_tina::rnd_next(byte *buf)
{
DBUG_ENTER("ha_tina::rnd_next");
statistic_increment(table->in_use->status_var.ha_read_rnd_next_count, &LOCK_status);
current_position= next_position;
if (!share->mapped_file)
DBUG_RETURN(HA_ERR_END_OF_FILE);
if (HA_ERR_END_OF_FILE == find_current_row(buf) )
DBUG_RETURN(HA_ERR_END_OF_FILE);
records++;
DBUG_RETURN(0);
}
对于从内部行格式åˆ�CSV行格式的转换,它是在find_current_row()函数中执行的ã€�
int ha_tina::find_current_row(byte *buf)
{
byte *mapped_ptr= (byte *)share->mapped_file + current_position;
byte *end_ptr;
DBUG_ENTER("ha_tina::find_current_row");
/* EOF should be counted as new line */
if ((end_ptr= find_eoln(share->mapped_file, current_position,
share->file_stat.st_size)) == 0)
DBUG_RETURN(HA_ERR_END_OF_FILE);
for (Field **field=table->field ; *field ; field++)
{
buffer.length(0);
mapped_ptr++; // Increment past the first quote
for(;mapped_ptr != end_ptr; mapped_ptr++)
{
// Need to convert line feeds!
if (*mapped_ptr == '"' &&
(((mapped_ptr[1] == ',') && (mapped_ptr[2] == '"')) ||
(mapped_ptr == end_ptr -1 )))
{
mapped_ptr += 2; // Move past the , and the "
break;
}
if (*mapped_ptr == '\\' && mapped_ptr != (end_ptr - 1))
{
mapped_ptr++;
if (*mapped_ptr == 'r')
buffer.append('\r');
else if (*mapped_ptr == 'n' )
buffer.append('\n');
else if ((*mapped_ptr == '\\') || (*mapped_ptr == '"'))
buffer.append(*mapped_ptr);
else /* This could only happed with an externally created file */
{
buffer.append('\\');
buffer.append(*mapped_ptr);
}
}
else
buffer.append(*mapped_ptr);
}
(*field)->store(buffer.ptr(), buffer.length(), system_charset_info);
}
next_position= (end_ptr - share->mapped_file)+1;
/* Maybe use \N for null? */
memset(buf, 0, table->s->null_bytes); /* We do not implement nulls! */
DBUG_RETURN(0);
}
对于使用共享访问方法的存储引擎(å¦�CSV引擎和其他示例引擎中显示的方法),必须将它们自己从共享结构中删除ï¼�
int ha_tina::close(void)
{
DBUG_ENTER("ha_tina::close");
DBUG_RETURN(free_share(share));
}
对于使用其自己共享管理系统的存储引擎,应使用任何所需的方法,在它们的处理程序中,从已打开表的共享区删除处理程序实例ã€�
所有的INSERT操作均是通过write_row()函数予以处理的:
int ha_foo::write_row(byte *buf)
*buf参数包含将要插入的行,采用内éƒ�MySQL格式。基本的存储引擎将简单地前进到数据文件末尾,并直接在末尾处添加缓冲的内容,这样就能使行读取变得简单,这是因为,你可以读取行并将其直接传递到rnd_next()函数çš�缓冲参数中ã€�
写入行的进程与读取行的进程相反:ä»�MySQL内部行格式获取数据,并将其写入数据文件。下述示例来è‡�CSV存储引擎ï¼�
int ha_tina::write_row(byte * buf)
{
int size;
DBUG_ENTER("ha_tina::write_row");
statistic_increment(table->in_use->status_var.ha_write_count, &LOCK_status);
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT)
table->timestamp_field->set_time();
size= encode_quote(buf);
if (my_write(share->data_file, buffer.ptr(), size, MYF(MY_WME | MY_NABP)))
DBUG_RETURN(-1);
if (get_mmap(share, 0) > 0)
DBUG_RETURN(-1);
DBUG_RETURN(0);
}
前述示例中的两条注释包括,更新关于写入操作的表统计,以及在写入行之前设置时间戳ã€�
通过执行表扫描操作,在找到与UPDATE语句çš�WHERE子句匹配的行后,MySQL服务器将执行UPDATE语句ï¼�然后调用update_row()函数ï¼�
int ha_foo::update_row(const byte *old_data, byte *new_data)
*old_data参数包含更新前位于行中的数据ï¼�è€�*new_data参数包含行的新内容(采用MySQL内部行格式)ã€�
更新的执行取决于行格式和存储实施方式。某些存储引擎将替换恰当位置的数据,而其他实施方案则会删除已有的行,并在数据文件末尾添加新行ã€�
非事务性存储引擎通常会忽ç•�*old_data参数的内容,仅处ç��*new_data缓冲ã€�事务性存储引擎可能需要比较缓冲,以确定在上次回滚中出现了什么变化ã€�
如果正在更新的表中包含时间戳列,对时间戳的更新将ç”�update_row()调用管理ã€�下述示例来自CSV引擎ï¼�
int ha_tina::update_row(const byte * old_data, byte * new_data)
{
int size;
DBUG_ENTER("ha_tina::update_row");
statistic_increment(table->in_use->status_var.ha_read_rnd_next_count,
&LOCK_status);
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE)
table->timestamp_field->set_time();
size= encode_quote(new_data);
if (chain_append())
DBUG_RETURN(-1);
if (my_write(share->data_file, buffer.ptr(), size, MYF(MY_WME | MY_NABP)))
DBUG_RETURN(-1);
DBUG_RETURN(0);
}
请注意上例中的时间戳设置ã€�
MySQL服务器采用了ä¸�INSERT语句相同的方法来执行DELETE语句:服务器使用rnd_next()函数跳到要删除的行,然后调用delete_row()函数删除行ã€�
int ha_foo::delete_row(const byte *buf)
*buf参数包含要删除行的内容。对于大多数存储引擎,该参数可被忽略,但事务性存储引擎可能需要保存删除的数据,以供回滚操作使用ã€�
下述示例来自CSV存储引擎ï¼�
int ha_tina::delete_row(const byte * buf)
{
DBUG_ENTER("ha_tina::delete_row");
statistic_increment(table->in_use->status_var.ha_delete_count,
&LOCK_status);
if (chain_append())
DBUG_RETURN(-1);
--records;
DBUG_RETURN(0);
}
前述示例的步骤是更新delete_count统计,并记录计数ã€�
定义存储引擎所使用的文件扩展ã€�
| virtual const char ** bas_ext ( | ); |
| ; |
这是bas_ext方法。调用它,可ä¸�MySQL服务器提供存储引擎所使用的文件扩展列表。该列表将返回以Null终结的字符串数组ã€�
通过提供扩展列表,在很多情况下,存储引擎能省ç•�delete_table()函数,这æ˜�因为MySQL服务器将关闭所有对表的引用,并使用指定的扩展删除所有文件ã€�
该函数无参数ã€�
返回值是存储引擎扩展的以Null终结的字符串数组。下面给出了CSV引擎的示例:
static const char *ha_tina_exts[] =
{
".CSV",
NullS
};
static const char *ha_tina_exts[] =
{
".CSV",
NullS
};
const char **ha_tina::bas_ext() const
{
return ha_tina_exts;
}
static const char *ha_example_exts[] = {
NullS
};
const char **ha_example::bas_ext() const
{
return ha_example_exts;
}
关闭打开的表ã€�
| virtual int close ( | void); |
| void ; |
这是close方法ã€�
关闭表。这是释放任何已分配资源的恰当时机ã€�
ä»�sql_base.ccã€�sql_select.ccå’�table.cc调用它。在sql_select.cc中,它仅用于关闭临时表,或在将临时表转换ä¸�myisam表的过程中关闭表。关äº�sql_base.cc,请查看close_data_tables()ã€�
void
无返回值ã€�
取自CSV引擎的示例:
int ha_example::close(void)
{
DBUG_ENTER("ha_example::close");
DBUG_RETURN(free_share(share));
}
创建新表ã€�
| virtual int create ( | name, | |
| form, | ||
| info); |
| const char * | name ; |
| TABLE * | form ; |
| HA_CREATE_INFO * | info ; |
这是create方法ã€�
调用create()以创建表ã€�变量名称为表的名称。调ç”�create()时,不需要打开表。此外,由于已创建了.frm文件,不推荐调整create_infoã€�
ç”�ha_create_table()ä»�handle.cc中调用ã€�
name
form
info
无返回值ã€�
CSV搜索引擎示例ï¼�
int ha_tina::create(const char *name, TABLE *table_arg,
HA_CREATE_INFO *create_info)
{
char name_buff[FN_REFLEN];
File create_file;
DBUG_ENTER("ha_tina::create");
if ((create_file= my_create(fn_format(name_buff, name, "", ".CSV",
MY_REPLACE_EXT|MY_UNPACK_FILENAME),0,
O_RDWR | O_TRUNC,MYF(MY_WME))) < 0)
DBUG_RETURN(-1);
my_close(create_file,MYF(0));
DBUG_RETURN(0);
}
删除行ã€�
| virtual int delete_row ( | buf); |
| const byte * | buf ; |
这是delete_row方法ã€�
Buf包含删除行的副本。调用了当前行后,服务器将立刻调用它(通过前一ä¸�rnd_next()或索引调ç”�)。如果存在指向上一行的指针,或能够访问 主键,删除操作将更为容易。请记住,服务器不保证连续删除。可以使ç”�ORDER BY子句ã€�
åœ�sql_acl.ccå’�sql_udf.cc中调用,以管理内部的表信息ã€�åœ�sql_delete.ccã€�sql_insert.ccå’�sql_select.cc中调用ã€�åœ�sql_select中,它用于删除副本,而在插入操作中,它用äº�REPLACE调用ã€�
buf
无返回值ã€�
{ return HA_ERR_WRONG_COMMAND; }
用来è‡�bas_ext()的扩展删除所有文件ã€�
| virtual int delete_table ( | name); |
| const char * | name ; |
这是delete_table方法ã€�
用于删除表。调ç”�delete_table()时,所有已打开的对该表的引用均将被关闭(并释放全局共享的引用)。变量名称为表名。此时,需要删除任何已创建的文件ã€�
如果未实施它,将ä»�handler.cc调用默认çš�delete_table(),并ç”�bas_ext()返回的文件扩å±�删除所有文件。假定处理程序返回的扩展比文件实际使用的多ã€�
ç”�delete_tableå’�ha_create_table()ä»�handler.cc调用。如果为存储引擎指定äº�table_flag HA_DROP_BEFORE_CREATE,仅在创建过程中使用ã€�
name: 表的基本名称
· 如果成功地从base_ext删除了至å°�1个文件而且未出现除ENOENT之外的错误,返回0ã€�
· #: Error
为事务处理表锁定ã€�
| virtual int external_lock ( | thd, | |
| lock_type); |
| THD * | thd ; |
| int | lock_type ; |
这是external_lock方法ã€�
åœ�lock.ccä¸�â€�用于mysql的锁定函æ•�â€�一节,给出了关于该议题的额外注释,值的一读ã€�
在表上创建锁定。如果实施了能处理事务的存储引擎,请查看ha_berkely.cc,以了解如何执行该操作的方法。否则,应考虑在此调用flock()ã€�
ç”�lock_external()å’�unlock_external()ä»�lock.cc中调用。也能由copy_data_between_tables()ä»�sql_table.cc中调用ã€�
thd
lock_type
无返回值ã€�
{ return 0; }
将提示从服务器传递给存储引擎ã€�
| virtual int extra ( | operation); |
| enum ha_extra_function | operation ; |
这是extra方法ã€�
无论何时,当服务器希望将提示发送到存储引擎时,将调ç”�extra()ã€�MyISAM引擎实现了大多数提示ã€�ha_innodb.cc给出了最详尽的提示列表ã€�
operation
无返回值ã€�
默认情况下,存储引擎倾向于不实施任何提示ã€�
{ return 0; }
提示存储引擎通报统计信息ã€�
| virtual void info ( | uint); |
| uint ; |
这是info方法ã€�
::info()用于将信息返回给优化程序。目前,该表处理程序未实施实际需要的大多数字段ã€�SHOW也能利用该数据。注意,或许你打算在你的代码中包含下述内容â€�if (records > 2) records = 2”。原因在于,服务器仅优化具有一条记录的情形。如果在表扫描过程中,你不清楚记录的数目,最好将记录数设ä¸�2,以便能够返回尽可能多的所需记录。除了记录外,你或许还希望设置其他变量,包括:删除的记录ï¼�data_file_lengthï¼�index_file_lengthï¼�delete_lengthï¼�check_time。更多信息,请参è§�handler.h中的公共变量ã€�
在下述文件中调用ï¼�filesort.cc ha_heap.cc item_sum.cc opt_sum.cc sql_delete.cc sql_delete.cc sql_derived.cc sql_select.cc sql_select.cc sql_select.cc sql_select.cc sql_select.cc sql_show.cc sql_show.cc sql_show.cc sql_show.cc sql_table.cc sql_union.cc sql_update.cc
uint
无返回值ã€�
该示例取è‡�CSV存储引擎ï¼�
void ha_tina::info(uint flag)
{
DBUG_ENTER("ha_tina::info");
/* This is a lie, but you don't want the optimizer to see zero or 1 */
if (records < 2)
records= 2;
DBUG_VOID_RETURN;
}
打开表ã€�
| virtual int open ( | name, | |
| mode, | ||
| test_if_locked); |
| const char * | name ; |
| int | mode ; |
| uint | test_if_locked ; |
这是open方法ã€�
用于打开表。名称是文件的名称。在需要打开表时打开它。例如,当请求在表上执行选择操作时(对于每一请求,表未打开并被关闭,对其进行高速缓冲处理)ã€�
ç”�handler::ha_open()ä»�handler.cc中调用。通过调用ha_open(),然后调用处理程序相关的open(),服务器打开所有表ã€�
对于处理程序对象,将作为初始化的一部分并在将其用于正常查询之前打开它(并非总在元数据变化之前)。如果打开了对象,在删除之前还将关闭它ã€�
这是open方法。调ç”�open以打开数据库表ã€�
ç¬�1个参数是要打开的表的名称。第2个参数决定了要打开的文件或将要执行的操作。这类值定义于handler.h中,为了方便起见在此列出ï¼�
#define HA_OPEN_KEYFILE 1
#define HA_OPEN_RNDFILE 2
#define HA_GET_INDEX 4
#define HA_GET_INFO 8 /* do a ha_info() after open */
#define HA_READ_ONLY 16 /* File opened as readonly */
#define HA_TRY_READ_ONLY 32 /* Try readonly if can't open with read and write */
#define HA_WAIT_IF_LOCKED 64 /* Wait if locked on open */
#define HA_ABORT_IF_LOCKED 128 /* skip if locked on open.*/
#define HA_BLOCK_LOCK 256 /* unlock when reading some records */
#define HA_OPEN_TEMPORARY 512
最后的选项规定了在打开表之前是否应检查表上的锁定ã€�
典型情况下,存储引擎需要实现某种形式的共享访问控制,以防止多线程环境下的文件损坏。关于如何实现文件锁定的示例,请参见sql/examples/ha_tina.ccçš�get_share()å’�free_share()方法ã€�
name
mode
test_if_locked
无返回值ã€�
该示例取è‡�CSV存储引擎ï¼�
int ha_tina::open(const char *name, int mode, uint test_if_locked)
{
DBUG_ENTER("ha_tina::open");
if (!(share= get_share(name, table)))
DBUG_RETURN(1);
thr_lock_data_init(&share->lock,&lock,NULL);
ref_length=sizeof(off_t);
DBUG_RETURN(0);
}
为表扫描功能初始化处理程序ã€�
| virtual int rnd_init ( | scan); |
| bool | scan ; |
这是rnd_init方法ã€�
当系统希望存储引擎执行表扫描时,将调ç”�rnd_init()ã€�
ä¸�index_init()不同ï¼�rnd_init()可以调用两次,两次调用之间不使用rnd_end()(仅å½�scan=1时才有意义)。随后,ç¬�2次调用应准备好新的表扫描。例如,如果rnd_init分配了光标,ç¬�2次调用应将光标定位于表的开始部分,不需要撤销分配并再次分配ã€�
从下述文件调用:filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, å’�sql_update.ccã€�
scan
无返回值ã€�
该示例取è‡�CSV存储引擎ï¼�
int ha_tina::rnd_init(bool scan)
{
DBUG_ENTER("ha_tina::rnd_init");
current_position= next_position= 0;
records= 0;
chain_ptr= chain;
DBUG_RETURN(0);
}
从表中读取下一行,并将其返回服务器ã€�
| virtual int rnd_next ( | buf); |
| byte * | buf ; |
这是rnd_next方法ã€�
对于表扫描的每一行调用它。耗尽记录时,应返å›�HA_ERR_END_OF_FILE。用行信息填å…�buff。表的字段结构是以服务器能理解的方式将数据保存到buf中的键ã€�
从下述文件调用:filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, å’�sql_update.ccã€�
buf
无返回值ã€�
下述示例取自ARCHIVE存储引擎ï¼�
int ha_archive::rnd_next(byte *buf)
{
int rc;
DBUG_ENTER("ha_archive::rnd_next");
if (share->crashed)
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
if (!scan_rows)
DBUG_RETURN(HA_ERR_END_OF_FILE);
scan_rows--;
statistic_increment(table->in_use->status_var.ha_read_rnd_next_count,
&LOCK_status);
current_position= gztell(archive);
rc= get_row(archive, buf);
if (rc != HA_ERR_END_OF_FILE)
records++;
DBUG_RETURN(rc);
}
创建和释放表锁定ã€�
| virtual THR_LOCK_DATA ** store_lock ( | thd, | |
| to, | ||
| lock_type); |
| THD * | thd ; |
| THR_LOCK_DATA ** | to ; |
| enum thr_lock_type | lock_type ; |
这是store_lock方法ã€�
下面介绍了关äº�handler::store_lock()的概念:
该语句决定了在表上需要何种锁定。对äº�updates/deletes/inserts,我们得åˆ�WRITE锁定;对äº�SELECT...,我们得到读锁定ã€�
将锁定添加到表锁定处理程序之前(请参è§�thr_lock.c),mysqld将用请求的锁定调用存储锁定。目前,存储锁定能将写锁定更改为读锁定(或某些其他锁定),忽略锁定(如果不打算使ç”�MySQL表锁定),或为很多表添加锁定(就像使ç”�MERGE处理程序时那样)ã€�
例如ï¼�Berkeley DB能够将所有的WRITE锁定更改ä¸�TL_WRITE_ALLOW_WRITE(表明正在执è¡�WRITES操作,但我们仍允许其他人执行操作)ã€�
释放锁定时,也将调用store_lock()。在这种情况下,通常不需要作任何事ã€�
在某些特殊情况下ï¼�MySQL可能会发送对TL_IGNORE的请求。这意味着我们正在请求与上次相同的锁定,这也应被忽略(当我们打开了表的某一部分时,如果其他人执行了表刷新操作,就会出现该情况,此时ï¼�mysqld将关闭并再次打开表,然后获取与上次相同的锁定)。我们打算在将来删除该特性ã€�
ç”�get_lock_data()ä»�lock.cc中调用ã€�
thd
to
lock_type
无返回值ã€�
下述示例取自ARCHIVE存储引擎ï¼�
/*
Below is an example of how to setup row level locking.
*/
THR_LOCK_DATA **ha_archive::store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type)
{
if (lock_type == TL_WRITE_DELAYED)
delayed_insert= TRUE;
else
delayed_insert= FALSE;
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
{
/*
Here is where we get into the guts of a row level lock.
If TL_UNLOCK is set
If we are not doing a LOCK TABLE or DISCARD/IMPORT
TABLESPACE, then allow multiple writers
*/
if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
lock_type <= TL_WRITE) && !thd->in_lock_tables
&& !thd->tablespace_op)
lock_type = TL_WRITE_ALLOW_WRITE;
/*
In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
MySQL would use the lock TL_READ_NO_INSERT on t2, and that
would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
to t2. Convert the lock to a normal read lock to allow
concurrent inserts to t2.
*/
if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables)
lock_type = TL_READ;
lock.type=lock_type;
}
*to++= &lock;
return to;
}
更新已有行的内容ã€�
| virtual int update_row ( | old_data, | |
| new_data); |
| const byte * | old_data ; |
| byte * | new_data ; |
这是update_row方法ã€�
old_data将保存前一行的记录,è€�new_data将保存最新的数据ã€�
如果使用äº�ORDER BY子句,服务器能够根据排序执行更新操作。不保证连续排序ã€�
目前ï¼�new_data不会拥有已更新的auto_increament记录,或已更新的时间戳字段。你可以通过下述方式(例如)完成该操作:if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) table->timestamp_field->set_time(); if (table->next_number_field && record == table->record[0]) update_auto_increment();
ä»�sql_select.cc, sql_acl.cc, sql_update.ccå’�sql_insert.cc调用ã€�
old_data
new_data
无返回值ã€�
{ return HA_ERR_WRONG_COMMAND; }
为表添加新行ã€�
| virtual int write_row ( | buf); |
| byte * | buf ; |
这是write_row方法ã€�
write_row()用于插入行。目前,如果出现大量加载,不会给出任ä½�extra()提示ã€�buf是数据的字节数组,大小为table->s->reclengthã€�
可以使用字段信息从本地字节数组类型提取数据。例如:
for (Field **field=table->field ; *field ; field++) { ... }
BLOB必须特殊处理ï¼�
for (ptr= table->s->blob_field, end= ptr + table->s->blob_fields ; ptr != end ; ptr++)
{
char *data_ptr;
uint32 size= ((Field_blob*)table->field[*ptr])->get_length();
((Field_blob*)table->field[*ptr])->get_ptr(&data_ptr);
...
}
关于以字符串形式提取所有数据的示例,请参见ha_tina.cc。在ha_berkeley.cc中,对于ha_berkeley自己的本地存储类型,给出了一个通过“包装功能”完整保存它的例子ã€�
请参è§�update_row()关于auto_increments和时间戳的注释。该情形也适用äº�write_row()ã€�
ä»�item_sum.ccã€�item_sum.ccã€�sql_acl.ccã€�sql_insert.ccã€�sql_insert.ccã€�sql_select.ccã€�sql_table.ccã€�sql_udf.cc、以å��sql_update.cc调用ã€�
数据çš�buf字节数组
无返回值ã€�
{ return HA_ERR_WRONG_COMMAND; }
这是MySQL参考手册的翻译版本,关于MySQL参考手册,请访é—�dev.mysql.comã€� 原始参考手册为英文版,与英文版参考手册相比,本翻译版可能不是最新的ã€�