目录
Mysql数据库API库
编写hello应用链接函数库
MySQL API常用函数
总体印象
初始化
Makefile 管理
连接数据库关闭连接
读取数据
查询表数据
获取结果集
解析结果集
释放结果集
获取列数
获取表头
实例程序
MySQL tools实现
思路分析
程序实现
中文问题
预处理类API函数
日期时间类API函数
多查询执行的C API函数
MySQL中的事务
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)
/// 查看头文件目录sudo find /usr/include -name mysql.h usr/include/mysql/mysql.h
/// 查找库文件目录sudo find /usr/ -name *libmysql* /usr/lib/x86_64-linux-gnu/libmysqlclient.a
// gcc -o mysql_inti mysql_init.c -I/usr/include/mysql -L//usr/lib/x86_64-linux-gnu -lmysqlclient
#include "stdlib.h"
#include "stdio.h"
#include <mysql/mysql.h>
int main()
{
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init error\n");
return -1;
}
printf("mysql init ok\n");
}
编译出错,原因是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 管理
//makefile实际需要去除注释 这里加上只是为了更好的理解
//书写makefile时候不能直接这样书写注释
//获取当前文件夹的所有.c文件
src = $(wildcard *.c)
//将.c文件.c去除
target = $(patsubst %.c, %, $(src))
//头文件和库文件目录
inc_path = /usr/include/mysql/
lib_path = /usr/lib/x86_64-linux-gnu/
all: $(target)
//依赖关闭 $<第一个依赖 $@目标
%:%.c
gcc $< -o $@ -I$(inc_path) -L$(lib_path) -lmysqlclient
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数据库 连接成功返回第一个参数 失败返回null
// 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_real_connect(&mysql,"host","user","passwd","database",0,NULL,0);
#include "stdlib.h"
#include "stdio.h"
#include <mysql/mysql.h>
int main()
{
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init error\n");
return -1;
}
// 连接数据库
// localhost 127.0.0.1都可以
MYSQL *MysqlCon = mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "mydb2", 0, NULL, 0);
if (MysqlCon == NULL)
{
printf("mysql_real_connect error,[%s]\n", mysql_error(mysql));
return -1;
}
// 打印地址
printf("mysql connect ok.mysql:[%p], conmysl:[%p]\n", mysql, MysqlCon);
// 关闭数据库
mysql_close(mysql);
return 0;
}
读取数据
查询表数据
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_query()不能用于包含二进制数据的查询,应使用mysql_real_query()取而代之(二进制数据可能包含字符‘\0’,mysql_query()会将该字符解释为查询字符串结束)。
// int mysql_query(MYSQL *mysql, const char *query)
// 返回值 如果查询成功,返回0。如果出现错误,返回非0值。
#include "stdlib.h"
#include "stdio.h"
#include <mysql/mysql.h>
int main()
{
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init error\n");
return -1;
}
printf("mysql init ok\n");
// 连接数据库
// localhost 127.0.0.1都可以
MYSQL *MysqlCon = mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "mydb2", 0, NULL, 0);
if (MysqlCon == NULL)
{
printf("mysql_real_connect error,[%s]\n", mysql_error(mysql));
return -1;
}
char Sql[255] = "insert into mytable values(2, 'feng')";
int ret = mysql_query(mysql, Sql);
if (ret != 0)
{
printf("query error,%s\n", mysql_error(mysql));
return -1;
}
printf("query ok\n");
return 0;
}
获取结果集
一种方式是通过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");
实例程序
/*
在相同的连接上,两个线程不能同时将查询发送到MySQL服务器。尤其是,必须确保在mysql_query()和mysql_store_result()之间,
没有使用相同连接的其他线程。
很多线程均能访问由mysql_store_result()检索的不同结果集。
如果使用了mysql_use_result,务必确保无其他线程正在使用相同的连接,直至关闭了结果集为止。
然而,对于线程式客户端,最好是共享相同的连接以使用mysql_store_result()。
查询函数
MYSQL_RES *mysql_store_result(MYSQL *mysql)
对于成功检索了数据的每个查询(SELECT、SHOW、DESCRIBE、EXPLAIN、CHECK TABLE等),必须调用mysql_store_result()或mysql_use_result() 。
对于其他查询,不需要调用mysql_store_result()或mysql_use_result(),但是如果在任何情况下均调用了mysql_store_result(),它也不会导致任何伤害或性能降低。
通过检查mysql_store_result()是否返回0,可检测查询是否没有结果集(以后会更多)。
mysql_store_result()将查询的全部结果读取到客户端,分配1个MYSQL_RES结构,并将结果置于该结构中。
如果查询未返回结果集,mysql_store_result()将返回Null指针(例如,如果查询是INSERT语句)。
如果读取结果集失败,mysql_store_result()还会返回Null指针。通过检查mysql_error()是否返回非空字符串,mysql_errno()是否返回非0值,
或mysql_field_count()是否返回0,可以检查是否出现了错误。
获取每一行函数
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
描述
检索结果集的下一行。在mysql_store_result()之后使用时,如果没有要检索的行,mysql_fetch_row()返回NULL。
在mysql_use_result()之后使用时,如果没有要检索的行或出现了错误,mysql_fetch_row()返回NULL。
获取列数
unsigned int mysql_field_count(MYSQL *mysql) 从mysql句柄中获取有多少列。
unsigned int mysql_num_fields(MYSQL_RES *result) 从返回的结果集中获取有多少列。
获取表头
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result) 全部获取
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result) 获取单个
销毁数据集函数
void mysql_free_result(MYSQL_RES *result)
释放由mysql_store_result()、mysql_use_result()、mysql_list_dbs()等为结果集分配的内存。
完成对结果集的操作后,必须调用mysql_free_result()释放结果集使用的内存。
*/
#include "stdlib.h"
#include "stdio.h"
#include <mysql/mysql.h>
int main()
{
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init error\n");
return -1;
}
printf("mysql init ok\n");
// 连接数据库
// localhost 127.0.0.1都可以
MYSQL *MysqlCon = mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "mydb2", 0, NULL, 0);
if (MysqlCon == NULL)
{
printf("mysql_real_connect error,[%s]\n", mysql_error(mysql));
return -1;
}
// 执行sql语句
int i = 0;
char Sql[255] = "select * from mytable";
int ret = mysql_query(mysql, Sql);
if (ret != 0)
{
printf("query error,%s\n", mysql_error(mysql));
return -1;
}
// 首先执行sql语句,后获取结果集
// 获取结果集
MYSQL_RES *results = mysql_store_result(mysql);
if (!results)
{
printf("mysql_store_result error,[%s]\n", mysql_error(mysql));
return -1;
}
// 获取列数
unsigned int num = mysql_num_fields(results);
// 打印表头
MYSQL_FIELD *mysql_field = mysql_fetch_fields(results);
if (mysql_field == NULL)
{
printf("mysql_field error,[%s]\n", mysql_error(mysql));
return -1;
}
for (i = 0; i < num; i++)
{
printf("%s\t", mysql_field[i].name);
}
printf("\n");
// 获取每一行数据
MYSQL_ROW row;
while ((row = mysql_fetch_row(results)))
{
for (i = 0; i < num; i++)
{
printf("%s\t", row[i]);
}
printf("\n");
}
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语句时不要在结尾加“;”
程序实现
// 使用Mysql客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mysql/mysql.h>
int main()
{
// 初始化Mysql
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init error\n");
return -1;
}
// 连接数据库
// localhost 127.0.0.1都可以
MYSQL *MysqlCon = mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "mydb2", 0, NULL, 0);
if (MysqlCon == NULL)
{
printf("mysql_real_connect error,[%s]\n", mysql_error(mysql));
return -1;
}
// 获取当前进程使用的字符集
printf("before:[%s]\n", mysql_character_set_name(mysql));
// 设置字符集为utf8格式 设置为utf-8可以显示中文字符
mysql_set_character_set(mysql, "utf8");
printf("after:[%s]\n", mysql_character_set_name(mysql));
int i;
int n;
int ret;
int num;
char *p;
char buf[1024];
MYSQL_RES *results;
MYSQL_FIELD *fields;
MYSQL_ROW row;
// 读取输入的指令 进入循环等待用户输入sql并执行
while (1)
{
// 打印提示符 不适用printf是因为printf需要输入回车键才能进行输出 STDOUT_FILENO为标准输出
write(STDOUT_FILENO, "mysql> ", strlen("mysql> "));
// 读取用户输入
memset(buf, 0x00, sizeof(buf));
read(STDIN_FILENO, buf, sizeof(buf));
// 将后面的;去除 因为mysql_query最好不要有; strrchr从后往前找字符 找到后返回这个字符的指针
p = strrchr(buf, ';');
if (p != NULL)
{
// 将后面的字符制空
*p = '\0';
}
// 去除回车
if (buf[0] == '\n')
{
continue;
}
// 去除前面的空格
// memmove用于拷贝字节,如果目标区域和源区域有重叠的话,
// memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改 但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
for (i = 0; i < strlen(buf); i++)
{
if (buf[i] != ' ')
{
break;
}
}
n = strlen(buf);
// 目标区域和源区域有重叠的时候使用memmove 表示将buf移动到buf + i位置 移动n-i+1个字符
memmove(buf, buf + i, n - i + 1); //+1表示多拷贝一个\0
printf("[%s]\n", buf);
// 判断输入的是否是exit 或 quit 如果是则退出程序
// strncasecmp不区分大小写进行比较
if (strncasecmp(buf, "exit", 4) == 0 || strncasecmp(buf, "quit", 4) == 0)
{
// 关闭数据库
mysql_close(mysql);
exit(0);
}
// 执行sql语句
ret = mysql_query(mysql, buf);
if (ret != 0)
{
// 如果sql语句为错误的 直接跳过改语句
printf("query error,%s\n", mysql_error(mysql));
continue;
return -1;
}
// 判断输入的是否为select指令
if (strncasecmp(buf, "select", 6) != 0)
{
// 不是则打印影响的行数
printf("Query OK, %ld row affected\n", mysql_affected_rows(mysql));
continue;
}
// 首先执行sql语句,后获取结果集
// 获取结果集
results = mysql_store_result(mysql);
if (!results)
{
printf("mysql_store_result error,[%s]\n", mysql_error(mysql));
return -1;
}
// 获取列数
num = mysql_num_fields(results);
// 打印表头
fields = mysql_fetch_fields(results);
if (fields == NULL)
{
printf("mysql_field error,[%s]\n", mysql_error(mysql));
mysql_free_result(results);
return -1;
}
for (i = 0; i < num; i++)
{
printf("%s\t", fields[i].name);
}
printf("\n");
// 获取每一行数据
while ((row = mysql_fetch_row(results)))
{
for (i = 0; i < num; i++)
{
printf("%s\t", row[i]);
}
printf("\n");
}
// 释放结果集
mysql_free_result(results);
}
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 <mysql/mysql.h>
#include <stdlib.h>
#include <string.h>
/*
预处理主要用于执行相同语句可以进行加速处理
大数据存储时也会进行使用 因为mysql_query()函数不能一次性将大数据插入到mysql数据库中
使用预处理可以一部分一部分进行插入
*/
#define _HOST_ "localhost" // 主机
#define _USER_ "root" // mysql用户,非主机
#define _PASSWD_ "123456" // 密码
#define _DBNAME_ "mydb2" // 库名
#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(?,?,?)"
void prepare_insert(MYSQL *mysql);
int main()
{
// 1.初始化
MYSQL *mysql = NULL;
mysql = mysql_init(NULL);
if (mysql == NULL)
{
printf("mysql init err\n");
exit(1);
}
// 2.连接
mysql = mysql_real_connect(mysql, _HOST_, _USER_, _PASSWD_, _DBNAME_, 0, NULL, 0);
if (mysql == NULL)
{
printf("mysql_real_connect connect err\n");
exit(1);
}
printf("welcome to mysql \n");
prepare_insert(mysql);
// 3.关闭
mysql_close(mysql);
return 0;
}
void prepare_insert(MYSQL *mysql)
{
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;
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);
}
// INSERT_SAMPLE ->"INSERT INTO test_table(col1,col2,col3) VALUES(?,?,?)"不完整的sql语句
if (mysql_stmt_prepare(stmt, INSERT_SAMPLE, strlen(INSERT_SAMPLE))) // insert 语句 的预处理
{
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; // char 100
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; // 是否为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);
}
// 第一波赋值
int_data = 10; /* integer */
strncpy(str_data, "MySQL", STRING_SIZE); /* string */
str_length = strlen(str_data);
/* INSERT SMALLINT data as NULL */
is_null = 1; // 指示插入的第三个字段是否为null
/* 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);
}
// 第二波赋值
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);
}
}
日期时间类API函数
练习:熟悉上述预处理类工作模式,模拟精简一个将时间插入数据库的程序。将时间存入数据库有两种方式:
1. 使用SQL语句方式
2. 预处理环境句柄变量方式存入
提示:
MYSQL_TIME ts; 浏览头文件 mysql_time.h 熟悉MYSQL_TIME结构体。
MYSQL_BIND bind[3];
MYSQL_STMT *stmt;
可直接使用SQL语句提前创建表test_table2,也可以使用mysql_query函数来创建。
create table test_table2 (date_field date, time_field time, timestamp_field timestamp);
char query[1024] = "INSERT INTO test_table2(date_field, time_field, timestamp_field) VALUES(?,?,?)";
stmt = mysql_stmt_init(mysql);
MYSQL_TIME 是一个结构体,使用typedef定义。位于mysql_time.h文件中。
API参考:refman-5.6-en.a4.pdf手册25.2.10. 日期和时间值的C API处理
多查询执行的C API函数
一次性执行多条SQL语句,包括select、drop、update、create等。 如:
mysql_query(mysql,"DROP TABLE IF EXISTS test_table;\
CREATE TABLE test_table(id INT);\
INSERT INTO test_table VALUES(10);\
UPDATE test_table SET id=20 WHERE id=10;\
SELECT * FROM test_table;\
DROP TABLE test_table");
文档:25.2.9. 多查询执行的C API处理。中文文档只有demo框架。查阅对应英文文档refman-5.6-en.a4.pdf。关键字Multiple 23.8.17
注意:打桩函数——函数接口
if (mysql_real_connect (mysql, host_name, user_name, password,
db_name, port_num, socket_name, CLIENT_MULTI_STATEMENTS) == NULL)
CLIENT_MULTI_STATEMENTS:客户端通知Server,将要发送多个SQL语句。
mysql_field_count(mysql):影响的行数。 如:
当select * from dept; 执行结束,提示:“5 rows in set” 表示影响了4行。
当Create一张表, 执行结束,提示:“Query OK, 0 rows affected (0.01 sec)”
当delete一行, 执行结束,提示:“Query OK, 1 row affected (0.00 sec)”
mysql_field_count函数调用后会将影响的行数保存到句柄 mysql 中。
将帮助文档中的demo导入程序,分析与我们之前掌握的API函数间的区别与联系:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mysql.h"
void process_result_set(MYSQL *mysql, MYSQL_RES *result)
{
int i, num;
num = mysql_field_count(mysql);
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;
while ((row = mysql_fetch_row(result))) {
for (i = 0; i < num; i++) {
printf("%10s\t", row[i]);
}
printf("\n");
}
}
int main(void)
{
int ret = 0, status = 0;
MYSQL_RES *result = NULL;
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;
}
mysql = mysql_real_connect(mysql, "localhost", "root", "123456", "mydb61", 0, NULL, CLIENT_MULTI_STATEMENTS);
if (mysql == NULL) {
ret = mysql_errno(mysql);
printf("mysql_init err:%d\n", ret);
return ret;
}
/以下为demo源码//
/* execute multiple statements */
status = mysql_query(mysql,"DROP TABLE IF EXISTS test_table;\
CREATE TABLE test_table(id INT);\
INSERT INTO test_table VALUES(10);\
UPDATE test_table SET id=20 WHERE id=10;\
SELECT * FROM test_table;");
DROP TABLE test_table
if (status)
{
printf("Could not execute statement(s)");
mysql_close(mysql);
exit(0);
}
/* process each statement result */
do {
/* did current statement return data? */
result = mysql_store_result(mysql);
if (result)
{
/* yes; process rows and free the result set */
process_result_set(mysql, result);
mysql_free_result(result);
}
else /* no result set or error */
{
if (mysql_field_count(mysql) == 0)
{
printf("%lld rows affected\n",
mysql_affected_rows(mysql));
}
else /* some error occurred */
{
printf("Could not retrieve result set\n");
break;
} }
/* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
if ((status = mysql_next_result(mysql)) > 0)
printf("Could not execute statement\n");
printf("------------status: %d\n", status);
} while (status == 0);
mysql_close(mysql);
return 0;
}
process_result_set函数是文档中给我们预留的打桩函数,需要我们在使用的过程中,自己实现它。
函数实现就是借助mysql和result两个参数打印一条sql语句查询到的结果集到屏幕。
可以直接使用mysq_tool.c中if (strncmp(sqlbuf, "select", 6) == 0 || strncmp(sqlbuf, "SELECT", 6) == 0)内的代码。“获取结果集”片段可以删除。“释放结果集”片段可以删除。API示例中含有该部分内容。
常见错误:在process_result_set函数实现中,不要使用mysql_store_result(mysql)再次获取结果集, 该result已经在API函数接口传入,直接使用参数result即可。否则会出现【段错误】。
MySQL中的事务
测试MySQL中事务的特性。
MySQL的事务的默认自动提交的,每执行一个sql语句都自动commit
Oracle的事务是自动打开的(以你执行的一条DML语句为标志),但每次执行需要手动commit
在程序中设置autocommit修改MySQL事务的属性。
set autocommit = 0 禁止自动提交
set autocommit = 1 开启自动提交MySQL中InnoDB引擎才支持事务默认自动提交机制。MYISAM引擎不支持。
// mysql中的事务
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
#define SET_TRAN "SET AUTOCOMMIT=0" // 手动commit ————手动commit
#define UNSET_TRAN "SET AUTOCOMMIT=1" // 自动commit
#define _HOST_ "127.0.0.1"
#define _USER_ "root"
#define _PASSWD_ "123456"
#define _DBNAME_ "mydb2"
/*
自动提交会将执行的sql语句自动进行提交,因此不能进行回滚操作
设置为手动提交后可以对事务进行回滚操作 因此大多数企业都是采用手动提交的方法。
*/
/*
改案列主要测试的是事务
修改mysql为手工提交:
1 开启事务
start transaction;
2 设置手工提交
set autocommit=0;
修改mysql为自动提交
1 开启事务
start transaction;
2 设置自动提交
set autocommit=1;
设置事务手动提交之后,可以进行回滚操作
如果未设置 默认会自动提交 不能进行回滚操作
*/
// 设置事务为手动提交
int mysql_OperationTran(MYSQL *mysql)
{
//--开启事务
int ret = mysql_query(mysql, "start transaction");
if (ret != 0)
{
printf("mysql_OperationTran query start err: %s\n", mysql_error(mysql));
return ret;
}
//--设置事务为手动提交
ret = mysql_query(mysql, SET_TRAN); // set autocommmit = 0
if (ret != 0)
{
printf("mysql_OperationTran query set err: %s\n", mysql_error(mysql));
return ret;
}
return ret;
}
// 设置事务为自动提交
int mysql_AutoTran(MYSQL *mysql)
{
//--开启事务
int ret = mysql_query(mysql, "start transaction");
if (ret != 0)
{
printf("mysql_AutoTran query start err: %s\n", mysql_error(mysql));
return ret;
}
//--设置事务为自动提交
ret = mysql_query(mysql, UNSET_TRAN); //"set autocommit = 1"
if (ret != 0)
{
printf("mysql_AutoTran query set err: %s\n", mysql_error(mysql));
return ret;
}
return ret;
}
// 执行commit,手动提交事务
int mysql_Commit(MYSQL *mysql)
{
int ret = mysql_query(mysql, "COMMIT"); // 提交
if (ret != 0)
{
printf("commit err: %s\n", mysql_error(mysql));
return ret;
}
return ret;
}
// 执行rollback,回滚事务
int mysql_Rollback(MYSQL *mysql)
{
int ret = mysql_query(mysql, "ROLLBACK");
if (ret != 0)
{
printf("rollback err: %s\n", mysql_error(mysql));
return ret;
}
return ret;
}
#define DROP_SAMPLE_TABLE "DROP TABLE IF EXISTS test_table"
#define CREATE_SAMPLE_TABLE "CREATE TABLE test_table(col1 INT,\
col2 VARCHAR(10),\
col3 VARCHAR(10))"
#define sql01 "INSERT INTO test_table(col1,col2,col3) VALUES(10, 'AAA', 'A1')"
#define sql02 "INSERT INTO test_table(col1,col2,col3) VALUES(20, 'BBB', 'B2')"
#define sql03 "INSERT INTO test_table(col1,col2,col3) VALUES(30, 'CCC', 'C3')"
#define sql04 "INSERT INTO test_table(col1,col2,col3) VALUES(40, 'DDD', 'D4')"
int main(void)
{
int ret = 0;
MYSQL *mysql = mysql_init(NULL);
mysql = mysql_real_connect(mysql, _HOST_, _USER_, _PASSWD_, _DBNAME_, 0, NULL, 0);
if (mysql == NULL)
{
ret = mysql_errno(mysql);
printf("func mysql_real_connect() err:%d\n", ret);
return ret;
}
printf(" --- connect ok......\n");
// 执行删除表
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);
}
ret = mysql_OperationTran(mysql); // 开启事务,并修改事务属性为手动commit
if (ret != 0)
{
printf("mysql_OperationTran() err:%d\n", ret);
return ret;
}
ret = mysql_query(mysql, sql01); // 向表中插入第一行数据 ‘AAA’
if (ret != 0)
{
printf("mysql_query() err:%d\n", ret);
return ret;
}
ret = mysql_query(mysql, sql02); // 向表中插入第二行数据 ‘BBB’
if (ret != 0)
{
printf("mysql_query() err:%d\n", ret);
return ret;
}
ret = mysql_Commit(mysql); // 手动提交事务
if (ret != 0)
{
printf("mysql_Commit() err:%d\n", ret);
return ret;
}
//AAA BBB 进去了。
#if 0
ret = mysql_AutoTran(mysql); // =再次= 修改事务属性为【自动】commit
if (ret != 0)
{
printf("mysql_OperationTran() err:%d\n", ret);
return ret;
}
#else
ret = mysql_OperationTran(mysql); // =再次= 修改事务属性为【手动】commit
if (ret != 0)
{
printf("mysql_OperationTran() err:%d\n", ret);
return ret;
}
#endif
ret = mysql_query(mysql, sql03); // 向表中插入第三行数据 ‘CCC’
if (ret != 0)
{
printf("mysql_query() err:%d\n", ret);
return ret;
}
ret = mysql_query(mysql, sql04); // 向表中插入第四行数据 ‘DDD’
if (ret != 0)
{
printf("mysql_query() err:%d\n", ret);
return ret;
}
ret = mysql_Rollback(mysql); // 直接rollback操作
if (ret != 0)
{
printf("mysql_Rollback() err:%d\n", ret);
return ret;
}
// rollback操作是否能回退掉CCC、DDD的值,取决于事务属性。
mysql_close(mysql);
return 0;
}
对应参考API手册。中文:25.2.3.2. 英文:23.8.7.2