MySQL数据库API库
访问MySQL服务器,这需要使用mysqlclient库,MySQL的大多数客户端API(除Java和.NET)都是通过这个库来和MySQL服务器通讯的,而这个库正是使用C语言编写的。
可使用mysql -V 命令查看当前系统内所使用的mysql数据库版本信息。数据库版本为5.6.20版。因此,我们可从帮助手册refman-5.6-en.a4.pdf入手,了解学习MySQL C API使用的一般信息。
从API手册23.8中可获取信息,MySQL客户端使用 libmysqlclient 库内部的函数访问MySQL服务器。因此我们在编程过程中,如若使用到库内的函数,必须链接函数库,对应的要找到头文件所在目录位置、函数库路径。以便我们在使用gcc编译工具时可以填充参数-I、-L、-l。
从手册中可获知,函数库名为mysqlclient。
因此我们使用命令find / -name libmysqlclient* 查找该库的路径。得到 /usr/lib64/mysql/libmysqlclient.a。
nm /usr/lib64/mysql/libmysqlclient.a命令可查看库内包含的函数。
编写hello应用链接函数库
编写一个hello.c应用程序,链接使用该库。
用到头文件 <mysql.h> 可使用locate mysql.h查看其目录位置/usr/include/mysql/mysql.h。
编译引用了库的应用程序。
gcc hello.c -o hello -I/usr/include/mysql/ -L/usr/lib64/mysql/ -lmysqlclient
参见帮助手册refman-5.6-en.a4.pdf:23.8.4.3小节。
MySQL API常用函数
总体印象
使用MySQL库API函数的一般步骤:
a. 初始化. MYSQL *mysql_init(MYSQL *mysql)
b. 错误处理 unsigned int mysql_errno(MYSQL *mysql)
char *mysql_error(MYSQL *mysql);
c. 建立连接. MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd,const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag);
d. 执行SQL语句 int mysql_query(MYSQL *mysql, const char *stmt_str)
e. 获取结果 MYSQL_RES *mysql_store_result(MYSQL *mysql)
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
f. 释放内存 void mysql_free_result(MYSQL_RES *result)
g. 关闭连接 void mysql_close(MYSQL *mysql)
初始化
编写程序测试 初始化函数MYSQL *mysql_init(MYSQL *mysql)。
其中有一种新数据类型MYSQL。可在头文件mysql.h → 263. typedef struct st_mysql {...} MYSQL;找到其定义。是一个结构体。
处理错误码的函数:unsigned int mysql_errno(MYSQL *mysql)
#include <stdio.h>#include "mysql.h"int main(void){int i, ret = 0, num = 0;MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("init ok...\n");return 0;}
编译出错,原因是64位Linux环境下,动态库配置不完整。需手动指定编译所用的动态库。根据错误提示分析需要加入如下函数库:
1. __gxx_personality_v0 --> -lstdc++ 使用g++相关的环境
2. dlclose/dlopen/dlsym --> -ldl 完成用一个程序加载其他动态库的作用。
3. pthread_* --> -lpthread 线程库
4. `my_getsystime'/`clock_gettime' --> -lrt librt.so是glibc中对real-time的支持库
使用ldd命令可以查看该可执行文件运行所依赖的库文件。
Makefile 管理
src = $(wildcard *.c)
target = $(patsubst %.c, %, $(src))
inc_path = /usr/include/mysql/
lib_path = /usr/lib64/mysql/
all: $(target)
%:%.c
gcc $< -o $@ -I$(inc_path) -L$(lib_path) -lmysqlclient -lstdc++ -lpthread -ldl -lrt
clean:
-rm -rf $(target)
.PHONY: all clean
注意:在测试makefile时,应先使用-n参数,检查无误再执行。
连接数据库关闭连接
依据proc猜想应该是一个类似于connect的函数,查看API文档发现:mysql_connect();但该函数已经过时,应该使用手册中推荐的mysql_real_connect函数取而代之。
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
根据手册中的描述,我们可以使用基础的链接方式与MySQL数据库建立连接。
mysql = mysql_real_connect(mysql, "localhost", "root", "123456", "mydb61", 0, NULL, 0);
连接数据库成功。对表中数据进行访问,访问结束需调用void mysql_close(MYSQL *mysql) 函数关闭连接。该函数在断开连接的同时,还可以解除分配由mysql指向的连接句柄。
mysql_close(mysql);
读取数据
查询表数据
mysql_query函数不单单能完成查询sql的功能,还能完成非select语句在c程序中的执行。是一个十分万能的c程序中执行SQL语句的函数。并且该函数本身直接支持静态SQL。查询以\0结尾的字符串。如果语句中包含二进制数据,则需要调用mysql_real_query来执行查询语句。
函数原型:int mysql_query(MYSQL *mysql, const char *query); 成功返回0,失败返回非0
char *psql = "select * from emp";
ret = mysql_query(mysql, psql);
若执行的是UPDATE, DELETE或INSERT语句,则可通过mysql_affected_rows()获知受影响的记录数。
若执行的是SELECT语句,查询结束后,查询结果被保存在mysql句柄中。需要使用获取结果集的API函数将结果集获取出来。有两种方式可以获取结果集。
注意: mysql_query执行的SQL语句不应为语句添加终结分号(‘;’)或“\g”。
获取结果集
一种方式是通过mysql_store_result()将整个结果集全部取回来。另一种方式则是调用mysql_use_result()初始化获取操作,但暂时不取回任何记录。视结果集的条目数选择获取结果集的函数。两种方法均通过mysql_fetch_row()来访问每一条记录。
MYSQL_RES *mysql_store_result(MYSQL *mysql) 成功返回MYSQL_RES结果集指针,失败返回NULL。
MYSQL_RES是一个结构体类型,可以从mysql.h头文件中找到该结构体的定义:
mysql.h → 308. typedef struct st_mysql_res {...} MYSQL_RES;
整体获取的结果集,保存在 MYSQL_RES 结构体指针中,通过检查mysql_store_result()是否返回NULL,可检测函数执行是否成功:
MYSQL_RES *result = mysql_store_result(mysql);
if (result == NULL) {
ret = mysql_errno(mysql);
printf("mysql_store_result error: %s\n", mysql_error(mysql));
return ret;
}
该函数调用成功,则SQL查询的结果被保存在result中,但我们不清楚有多少条数据。所以应使用游标的方式将结果集中的数据逐条取出。
解析结果集
通过游标一行一行fetch结果集中的数据。根据游标使用的一般特性,应使用循环结构,到达结尾或者出错,返回NULL。
函数原型:MYSQL_ROW mysql_fetch_row(MYSQL_RES *result) 成功返回下一行的MYSQL_ROW结构。如果没有更多要检索的行或出现了错误,返回NULL。-----MYSQL_ROW定义在118行
select * from emp 可以看到emp表一共有8列数据。可以循环将每行上每一列的数据显示到屏幕。
MYSQL_ROW row = NULL; //typedef char **MYSQL_ROW;
while ((row = mysql_fetch_row(result))) {
printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7]);
}
MYSQL_ROW的本质是 typedef char ** MYSQL_ROW; 数据信息存储的形式如下图所示:
从mysql.h头文件可查看MYSQL_ROW定义: 118. typedef char **MYSQL_ROW; /*return data as array of string*/
从上图分析MYSQL_ROW为什么被定义为char**类型呢?推测mysq_fetch_row()的函数实现大致思想如下:
char **mysql_fetch_row(){char **tmp = (char **) malloc(sizeof(char *) * 8);for (i = 0; i < 8; i++) {tmp[i] = (char *)malloc(50);}strcpy(tmp[0], "7369");strcpy(tmp[1], "SMITH");strcpy(tmp[2], "CLERK");...return tmp;}
释放结果集
结果集处理完成,应调用对应的函数释放所占用的内存。
void mysql_free_result(MYSQL_RES *result); 成功释放参数传递的结果集。没有失败情况。
mysql_free_result(result);
思考:上述实现是直接在MySQL工具中数出列数。找寻能获取列数的API函数、获取表头的API函数。
获取列数
查看帮助手册可以看到,有两个函数具备获取列数的功能:
unsigned int mysql_field_count(MYSQL *mysql) 从mysql句柄中获取有多少列。
unsigned int mysql_num_fields(MYSQL_RES *result) 从返回的结果集中获取有多少列。
选择任意一种方式均可以完成该功能。
int num = mysql_field_count(connect);
while (row = mysql_fetch_row(result)) {
for (i = 0; i < num; i++) {
printf("%s\t", row[i]);
}
printf("\n");
//printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7]);
}
获取表头
获取表头的API函数同样有两个:
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result) 全部获取
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result) 获取单个
MYSQL_FIELD也是一个结构体类型,其内部保存了选择列表项的信息,其中的name成员变量就保存着列名。可从头文件mysql.h中94-116行找到其定义。
MYSQL_FIELD *fields = NULL;
fields = mysql_fetch_fields(result); //得到表头的结构体数组
for (i = 0; i < num; i++) { //已通过 mysql_field_count 获取了总列数
printf("%s\t", fields[i].name); //每一列的列名保存在name成员中
}
printf("\n");
示例程序
#include <stdio.h>#include "mysql.h"int main(void){int i, ret = 0, num = 0;char *psql = "select * from emp"; MYSQL_RES *result = NULL;MYSQL_FIELD *fields = NULL;MYSQL_ROW row = NULL;MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("init ok...\n");mysql = mysql_real_connect(mysql, "localhost", "root", "123456", "mydb61", 0, NULL, 0);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("connect ok...\n");ret = mysql_query(mysql, psql);if (ret != 0) {printf("mysql_query error: %s\n", mysql_error(mysql));return ret;}num = mysql_field_count(mysql);result = mysql_store_result(mysql);if (result == NULL) {ret = mysql_errno(mysql);printf("mysql_store_result error: %s\n", mysql_error(mysql));return ret;}fields = mysql_fetch_fields(result);for (i = 0; i < num; i++) {printf("%10s\t", fields[i].name);}printf("\n");while ((row = mysql_fetch_row(result))) {for (i = 0; i < num; i++) {printf("%10s\t", row[i]);}printf("\n");}mysql_free_result(result);mysql_close(mysql);return 0;}
MySQL tools实现
依托我们所学习的MySQL基础类API函数,可以编写程序实现简单的sqlplus/mysql 工具的功能。
思路分析
- 仿照mysql工具,应在连接数据库成功之后,在一个while循环中不断的接受用户输入的SQL语句。定义char sqlbuf[1024] 存储用户输入的SQL语句。初始化该buf,并提示用户输入SQL语句。使用gets函数在循环中动态接收用户输入。
while (1) {
memset(sqlbuf, 0, sizeof(sqlbuf));
printf("\nYourSQL> ");
fgets(sqlbuf, sizeof(sqlbuf), stdin);
}
- 在mysql_query(connect, sqlbuf)之前,如果用户输入了“exit”那么程序直接结束。
- 在执行完 mysql_query(connect, sqlbuf)之后,应该判别用户输入的是否为select语句。如不是select语句不需要查询结果集、处理结果集等繁复操作。
- 如用户输入的是有结果集的SQL语句,将获取列数、获取结果集、获取表头、解析结果集、释放结果集等相关代码一起并入if (strncmp(sqlbuf, "select", 6))中。
测试注意:执行SQL语句时不要在结尾加“;”
程序实现
#include <stdio.h>#include <stdlib.h>#include <string.h>#include "mysql.h"int main(void){int i, ret = 0, num = 0;//char *psql = "select * from emp"; char sqlbuf[1024];MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("init ok...\n");mysql = mysql_real_connect(mysql, "localhost", "root", "123456", "mydb61", 0, NULL, 0);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("connect ok...\n");ret = mysql_query(mysql, "set names utf8");if (ret != 0) {printf("mysql_query error: %s\n", mysql_error(mysql));return ret;}while (1) { memset(sqlbuf, 0, sizeof(sqlbuf));printf("YourSQL> ");fgets(sqlbuf, sizeof(sqlbuf), stdin);if (strncmp(sqlbuf, "exit", 4) == 0 || strncmp(sqlbuf, "quit", 4) == 0) {break;}ret = mysql_query(mysql, sqlbuf);if (ret != 0) {printf("mysql_query error: %s\n", mysql_error(mysql));return ret;}if (strncmp(sqlbuf, "select", 6) == 0 || strncmp(sqlbuf, "SELECT", 6) == 0) {num = mysql_field_count(mysql);MYSQL_RES *result = NULL;result = mysql_store_result(mysql);if (result == NULL) {ret = mysql_errno(mysql);printf("mysql_store_result error: %s\n", mysql_error(mysql));return ret;}//打印表头MYSQL_FIELD *fields = NULL;fields = mysql_fetch_fields(result);for (i = 0; i < num; i++) {printf("%10s\t", fields[i].name);}printf("\n");//解析结果集MYSQL_ROW row = NULL; //typedef char **MYSQL_ROW;while ((row = mysql_fetch_row(result))) {for (i = 0; i < num; i++) {printf("%10s\t", row[i]);}printf("\n");}mysql_free_result(result);} else {printf("\n--- not select sql---\n");printf("affected: %ld\n", (long)mysql_affected_rows(mysql));}}mysql_close(mysql);return 0;}
中文问题:
修改mysql_real_connect()参数,连接到表中有中文数据的数据库,如mydb2,执行程序,测试显示中文出现乱码。我们可以使用mysql_query函数来解决该问题。
在 while (1) 之前使用 ret = mysql_query(mysql, "set names utf8"); 来设置查询属性(也可以加到while中)。表示在查询的时候使用utf8的形式进行查询。
或者mysql_set_character_set(mysql, "utf8");
获取当前使用的字符集: const char *mysql_character_set_name(MYSQL *mysql)
预处理类API函数:
该类函数解决问题:处理带有占位符的SQL语句。insert into table111(col1, col2, col3) values(?, ?, ?);
这种SQL语句由两部分组成,一部分是SQL语句体模型部分,另一部分是?所匹配的值。
性能、调优是数据库编程永恒不变的主题!如果能把SQL语句框架预先处理好,当真正要执行SQL语句时只需要发送对应的参数到对应的SQL框架中,就能提高客户端访问服务器的速度,且数据量小,可以减少网络通信量,提高数据传输效率高。
元数据(Metadata):又称中介数据、中继数据,为描述数据的数据,主要是描述数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。
根据API提供的案例学习该部分内容。主要有 4 个函数:
mysql_stmt_init() 初始化预处理环境句柄。 返回一个结构体指针 MYSQL_STMT *stmt
mysql_stmt_prepare() 向上面句柄中添加SQL语句,带有 (?,?,?) 占位符
mysql_stmt_param_count() 求绑定变量的个数(辅助函数), 有多少个'?'就返回多少
mysql_stmt_bind_param() 将?对应的实参,设置到预处理环境句柄中
mysql_stmt_execute() 执行预处理的SQL语句
在不熟悉这套API函数的情况下,如何能快速的找到一个完整的案例,使用这套函数呢?分析:在以上4个过程中,哪个最重要呢?找到它,去查看API文档!发现有对应的demo程序。将该demo导入到我们的程序中,运行,观察它的作用。
#include <stdio.h>#include <stdlib.h>#include <string.h>#include "mysql.h"#define STRING_SIZE 50#define DROP_SAMPLE_TABLE "DROP TABLE IF EXISTS test_table"#define CREATE_SAMPLE_TABLE "CREATE TABLE test_table(col1 INT,\col2 VARCHAR(40),\col3 SMALLINT,\col4 TIMESTAMP)"#define INSERT_SAMPLE "INSERT INTO test_table(col1,col2,col3) VALUES(?,?,?)"int main(void){int ret = 0;MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {//unsigned int mysql_errno(MYSQL *mysql)ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("init ok...\n");mysql = mysql_real_connect(mysql, "localhost", "root", "123456", "mydb61", 0, NULL, 0);if (mysql == NULL) {ret = mysql_errno(mysql);printf("mysql_init err:%d\n", ret);return ret;}printf("connect ok...\n");以下为demo源码MYSQL_STMT *stmt;MYSQL_BIND bind[3];my_ulonglong affected_rows;int param_count;short small_data;int int_data;char str_data[STRING_SIZE];unsigned long str_length;my_bool is_null;if (mysql_query(mysql, DROP_SAMPLE_TABLE)){fprintf(stderr, " DROP TABLE failed\n");fprintf(stderr, " %s\n", mysql_error(mysql));exit(0);}if (mysql_query(mysql, CREATE_SAMPLE_TABLE)){fprintf(stderr, " CREATE TABLE failed\n");fprintf(stderr, " %s\n", mysql_error(mysql));exit(0);}/* Prepare an INSERT query with 3 parameters *//* (the TIMESTAMP column is not named; the server *//* sets it to the current date and time) */stmt = mysql_stmt_init(mysql);if (!stmt){fprintf(stderr, " mysql_stmt_init(), out of memory\n");exit(0);}if (mysql_stmt_prepare(stmt, INSERT_SAMPLE, strlen(INSERT_SAMPLE))){fprintf(stderr, " mysql_stmt_prepare(), INSERT failed\n");fprintf(stderr, " %s\n", mysql_stmt_error(stmt));exit(0);}fprintf(stdout, " prepare, INSERT successful\n");/* Get the parameter count from the statement */param_count= mysql_stmt_param_count(stmt);fprintf(stdout, " total parameters in INSERT: %d\n", param_count);if (param_count != 3) /* validate parameter count */{fprintf(stderr, " invalid parameter count returned by MySQL\n");exit(0);}/* Bind the data for all 3 parameters */memset(bind, 0, sizeof(bind));/* INTEGER PARAM *//* This is a number type, so there is no need to specify buffer_length */bind[0].buffer_type= MYSQL_TYPE_LONG;bind[0].buffer= (char *)&int_data;bind[0].is_null= 0;bind[0].length= 0;/* STRING PARAM */bind[1].buffer_type= MYSQL_TYPE_STRING;bind[1].buffer= (char *)str_data;bind[1].buffer_length= STRING_SIZE;bind[1].is_null= 0;bind[1].length= &str_length;/* SMALLINT PARAM */bind[2].buffer_type= MYSQL_TYPE_SHORT;bind[2].buffer= (char *)&small_data;bind[2].is_null= &is_null;bind[2].length= 0;/* Bind the buffers */if (mysql_stmt_bind_param(stmt, bind)){fprintf(stderr, " mysql_stmt_bind_param() failed\n");fprintf(stderr, " %s\n", mysql_stmt_error(stmt));exit(0);}/* Specify the data values for the first row */int_data= 10; /* integer */strncpy(str_data, "MySQL", STRING_SIZE); /* string */str_length= strlen(str_data);/* INSERT SMALLINT data as NULL */is_null= 1;/* Execute the INSERT statement - 1*/if (mysql_stmt_execute(stmt)){fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");fprintf(stderr, " %s\n", mysql_stmt_error(stmt));exit(0);}/* Get the total number of affected rows */affected_rows= mysql_stmt_affected_rows(stmt);fprintf(stdout, " total affected rows(insert 1): %lu\n",(unsigned long) affected_rows);if (affected_rows != 1) /* validate affected rows */{fprintf(stderr, " invalid affected rows by MySQL\n");exit(0);}/* Specify data values for second row, then re-execute the statement */int_data= 1000;strncpy(str_data, "The most popular Open Source database", STRING_SIZE);str_length= strlen(str_data);small_data= 1000; /* smallint */is_null= 0; /* reset *//* Execute the INSERT statement - 2*/if (mysql_stmt_execute(stmt)){fprintf(stderr, " mysql_stmt_execute, 2 failed\n");fprintf(stderr, " %s\n", mysql_stmt_error(stmt));exit(0);}/* Get the total rows affected */affected_rows= mysql_stmt_affected_rows(stmt);fprintf(stdout, " total affected rows(insert 2): %lu\n",(unsigned long) affected_rows);if (affected_rows != 1) /* validate affected rows */{fprintf(stderr, " invalid affected rows by MySQL\n");exit(0);}/* Close the statement */if (mysql_stmt_close(stmt)){fprintf(stderr, " failed while closing the statement\n");fprintf(stderr, " %s\n", mysql_stmt_error(stmt));exit(0);}mysql_close(mysql);printf("hello mysql...\n");return 0;}
注意:bind --> mysql.h下 MYSQL_BIND结构体 bind[3]; 是一个结构体数组。有3个‘?’占位符,所以用三个结构体(数组)来对应保存信息。0->第一列;1->第二列;2->第三列。
mysql.h中查找 MYSQL_BIND 结构体原型。对比:select * from teat_table; 和 desc test_table; 的查询结果。
帮助理解bind的小程序框架:
typedef struct xxx{char *p1;char *p2;char *p3;} MYSQL_BIND;void saveXXXInfo(MYSQL_BIND *bind, int num){insert into test_table valudes(bind[0].p2, bind[1].p3, bind[2].p1);}int main(void){MYSQL_BIND bind[3];bind[0].p1 = "类型1"; //第一列bind[0].p2 = "10";bind[0].p3 = "其他数据";bind[1].p1 = "类型"; //第二列bind[1].p2 = "数据";bind[1].p3 = "描述";bind[2].p1 = "p1p1p1"; //第三列 //第四列是时间戳,不需要用户使用?来指定,直接使用了系统时间。bind[2].p2 = "数据";bind[2].p3 = "其他限定条件";saveXXXInfo(bind, 3);}