Back

MySQL数据库API库

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, DELETEINSERT语句,则可通过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; 数据信息存储的形式如下图所示:

wps1.jpg

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 工具的功能。

思路分析

\1. 仿照mysql工具,应在连接数据库成功之后,在一个while循环中不断的接受用户输入的SQL语句。定义char sqlbuf[1024] 存储用户输入的SQL语句。初始化该buf,并提示用户输入SQL语句。使用gets函数在循环中动态接收用户输入。

while (1) {
    memset(sqlbuf, 0, sizeof(sqlbuf));

    printf("\nYourSQL> ");

    fgets(sqlbuf, sizeof(sqlbuf), stdin);
}
  1. 在mysql_query(connect, sqlbuf)之前,如果用户输入了“exit”那么程序直接结束。

  2. 在执行完 mysql_query(connect, sqlbuf)之后,应该判别用户输入的是否为select语句。如不是select语句不需要查询结果集、处理结果集等繁复操作。

  3. 如用户输入的是有结果集的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) || strncmp(sqlbuf, "quit", 4) ) {
            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) || strncmp(sqlbuf, "SELECT", 6)) {
            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语句

wps2.jpg

在不熟悉这套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);

}

日期时间类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.cif (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引擎不支持。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mysql.h" 
#define SET_TRAN	"SET AUTOCOMMIT=0"  	//手动commit	
#define UNSET_TRAN	"SET AUTOCOMMIT=1"		//自动commit 
//设置事务为手动提交

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);
    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);
    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, "localhost", "root", "123456", "mydb2", 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;
    }

    ret = mysql_AutoTran(mysql); 		// =再次= 修改事务属性为【自动】commit
    if (ret != 0) {
        printf("mysql_OperationTran() err:%d\n", ret);

        return ret;
    }

    ret = mysql_OperationTran(mysql); 	// =再次= 修改事务属性为【手动】commit
    if (ret != 0) {
        printf("mysql_OperationTran() err:%d\n", ret);

        return ret;
    }

    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