引言
关于Mysql,大家都已经非常熟悉了。但是这种C/S的数据服务不适合于一些轻量级的应用,很多情况下,我们希望能使用一个进程级的存储引擎,供我们查询一些关系数据,而非使用一套臃肿的服务。当然这时我们可以选择SqlLite,Berkeley DB,Access等轻量级的存储件,但是都有学习成本,我们能否采用我们已经非常熟悉的Mysql接口来实现呢?答案是肯定的,很多人可能不知道:Mysql其实可以将服务以library的形式编译,供程序单独使用,这样我们的程序相当于内联了一套Mysql服务。
编译
我们以linux下的编译为例介绍编译Embedded Mysql的步骤。
Mysql 5.0版本的编译
在Mysql 5.0及之前版本,都是采用autobuild工具编译,config时的参数可以如下配置:
./configure --prefix=/home/work/mysql --with-embedded-server --with-mysqld-ldflags --with-mysqld-ldflags=-all-static--enable-thread-safe-client --enable-local-infile --with-extra-charsets=complex--disable-shared |
上面的参数中:
- --prefix=/home/work/mysql 指定安装位置,可以根据自己的需要设置到其他目录。
- --with-embedded-server是编译embedded mysql的关键,这个参数默认是不开启的,因此一般只会编译出libmysqlclient.a(.so)这个客户端API库,而开启此参数后,会多生成一个libmysqld.a(.so)的库,这个库同时集成了mysql服务与客户端API。
- --with-mysqld-ldflags=-all-static与--disable-shared两个参数表示只编译静态库,如果需要使用动态库,也可以去掉这两个参数。
- -enable-thread-safe-client表示客户端API应该保持线程安全(但貌似Mysql 5.0有bug,即使开启此参数,有时也非线程安全)。
- --with-extra-charsets=complex 表示支持大字符集。
configure完成之后:
$ make $ make install |
即可以完成编译。
Mysql 5.5之后的版本编译
mysql 5.5之后的版本都迁移到采用cmake进行编译,因此机器需要先安装cmake,关于cmake的安装,在此不做介绍,可以参考:
http://www.cmake.org/cmake/resources/resources.html
安装cmake之后,可以用下面的命令编译
cmake . -DWITH_EMBEDDED_SERVER=1 -DCMAKE_INSTALL_PREFIX=/home/work/mysql -DCMAKE_BUILD_TYPE=Release |
上面的参数中:
- -DCMAKE_INSTALL_PREFIX指定安装位置,与5.0中的--prefix对应。
- -DDWITH_EMBEDDED_SERVER表示将编译Embedded Mysql库,与5.0中的--with-embedded-server对应。
- -DCMAKE_BUILD_TYPE表示生成库的方式,它有以下几种选项:
1. RelWithDebInfo 带调试信息,开启编译优化的库,编译参数为-O2 -g
2. Debug 带调试信息,不做任何编译优化的库,编译参数-g
3. Release 不带调试信息,编译优化的库,编译参数-O2
4. MinSizeRel 不带调试信息,最小size的库,编译参数-Os - Mysql5.5之后的版本编出的库都是线程安全的,所以不需要 类似-enable-thread-safe-client的参数
是否带调试信息对生成出库的大小影响很大,例如Debug库编译出来有70多兆,而Release编译出来只有不到20MB,如果不需要调试,建议已Release编译。
Embedded Mysql使用方法
我们编写如下代码(test.cpp)进行测试:
#include <stdio.h>
#include <mysql.h>
#include <stdlib.h>
#include <string.h>
int main()
{
MYSQL* sql;
MYSQL_STMT* stmt;
MYSQL_BIND bind;
unsigned* indice;
int ret;
int result;
unsigned long length;
char query1[] = "create database if not exists mydb";
char query2[] = "use mydb";
char query3[] = "create table if not exists mytable (id int not null, col int not
null)";
char query4[] = "insert into mytable (id, col) values (1,100)";
char query5[] = "select col from mytable where id = ?";
char *server_options[] = { "","--defaults-file=my.cnf","--basedir=./"};
int num_elements = sizeof(server_options)/ sizeof(char *);
if(mysql_server_init(num_elements, server_options, NULL))
{
puts("failed to initialize server");
return 1;
}
sql = mysql_init(NULL);
if(!mysql_real_connect(sql, NULL , NULL , NULL , NULL, 3306, NULL, 0))
printf("failed to connect\n");
if(mysql_query(sql, query1))
printf("failed to create database:%s\n", mysql_error(sql));
ret = mysql_query(sql, query2);
if(ret)
printf("failed to use database:%s\n", mysql_error(sql));
ret = mysql_query(sql, query3);
if(ret)
printf("failed to create table:%s\n", mysql_error(sql));
ret = mysql_query(sql, query4);
if(ret)
printf("failed to insert record:%s\n", mysql_error(sql));
stmt = mysql_stmt_init(sql);
if(!stmt)
printf("failed to init stmt:%s\n", mysql_error(sql));
ret = mysql_stmt_prepare(stmt, query5, strlen(query5));
if(ret)
printf("failed to prepare:%s\n", mysql_stmt_error(stmt));
result = 1;
memset(&bind, 0 , sizeof(bind));
bind.buffer_type = MYSQL_TYPE_LONG;
bind.is_unsigned = (my_bool)0;
bind.is_null = 0;
bind.buffer = (char*)&result;
bind.buffer_length = 4;
bind.length = &length;
ret = mysql_stmt_bind_param(stmt, &bind);
if(ret)
printf("failed to bind param:%s\n", mysql_stmt_error(stmt));
ret = mysql_stmt_execute(stmt);
if(ret)
printf("failed to execute stmt:%s\n", mysql_stmt_error(stmt));
memset(&bind, 0 , sizeof(bind));
bind.buffer_type = MYSQL_TYPE_LONG;
bind.is_unsigned = (my_bool)0;
bind.is_null = 0;
bind.buffer = (char*)&result;
bind.buffer_length = 4;
bind.length = &length;
ret = mysql_stmt_bind_result(stmt, &bind);
if(ret)
printf("failed to bind result:%s\n", mysql_stmt_error(stmt));
ret = mysql_stmt_store_result(stmt);
if(ret)
printf("failed to store result:%s\n", mysql_stmt_error(stmt));
ret = mysql_stmt_fetch(stmt);
if(ret)
printf("failed to fetch result:%s\n", mysql_stmt_error(stmt));
mysql_stmt_close(stmt);
mysql_close(sql);
mysql_server_end();
return 0;
}
从代码我们可以看出,使用embedded mysql的方法与使用一般的mysql API基本一致,但是需要在使用前后初始化库与清理环境,分别调用mysql_server_init()与mysql_server_end()这两个函数。其中,mysql_server_init中可以指定mysql服务的配置文件my.cnf以及数据生成的位置(也可以不在init中配置数据目录,在my.cnf文件中配置数据目录)。
相比正式的mysql服务,embedded mysql的my.cnf只支持[server]与[embedded]这两节配置,我们可以在当前目录下创建一个类似下面的my.cnf:
[server] [embedded] |
注意:上面的配置文件中,datadir指定mysql数据保存的位置(注意:该目录必须在程序启动前已存在,如果不存在需要提前手工创建,否则会启动失败)。language这个选项相当重要,它指定的是服务端与客户端各种错误信息的资源文件。我们可以在mysql安装目录的share目录下找到多种语言的资源文件,一般采用share/english/errmsg.sys即可(既可以直接将language指向这个目录,也可以将这个文件拷贝至当前目录下,然后将language指向目录设为./)。
配置好my.cnf之后,我们如下编译:
$ g++ test.cpp -o a.out -I/home/work/mysql/include -L/home/work/mysql/lib -lmysqld |
如果编译了动态链接库,生成的执行文件可能不能正常运行,需要在LD_LIBRARY_PATH中导出libmysqld.so的位置:
$ export LD_LIBRARY_PATH=/home/work/mysql/lib:$LD_LIBRARY_PATH |
编译生成a.out,执行即可查询。
注意事项
embedded mysql目前只能使用inno-db引擎,它会在datadir指定的目录下生成数据库文件,每个database用一个目录保存,我们也可以将这些数据库目录拷贝到真正的mysql服务的数据目录中,使用mysql工具进行查询(注意:如果这么操作,mysql服务必须重启)。
关于Mysql 5.5的bug
本节是我在使用embedded mysql过程中发现的一些bug,这些bug至少在Mysql 5.5.15版本中存在,我已向Mysql社区反映,在官方修复之前,建议在编译之前修改下面一些文件:
bug1:会导致多线程下的内存泄漏 | 在libmysqld/lib_sql.cc源代码的256行: stmt->fields= mysql->fields; 之后添加: free_root(&stmt->mem_root, MYF(0)); |
bug2:会导致多线程下的内存泄漏 | 在libmysqld/lib_sql.cc的356行(stmt->result= *data;语句之前)加: if(stmt->result.alloc.free != (*data).alloc.free || stmt->result.alloc.pre_alloc != (*data).alloc.pre_alloc) { free_root(&stmt->result.alloc, MYF(0)); } |
bug3:会导致某些查询失败 | 在libmysqld/lib_sql.cc的346行: if (res) { NET *net= &stmt->mysql->net; set_stmt_errmsg(stmt, net); DBUG_RETURN(1); } 之后,添加下面两行代码: |
结论
使用Embedded Mysql方案可以尽可能少的改动我们的代码,兼容Mysql服务,实现轻量级的关系数据库存储。
其他
作者:icefireelf