Linux下使用Mysql 第二天

目录

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应用程序,链接使用该库。                        

         用到头文件 可使用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 
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 
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 
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; 数据信息存储的形式如下图所示:

Linux下使用Mysql 第二天_第1张图片

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

思路分析

  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语句时不要在结尾加“;  

程序实现

// 使用Mysql客户端
#include 
#include 
#include 
#include 
#include 
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语句

Linux下使用Mysql 第二天_第2张图片  

         在不熟悉这套API函数的情况下,如何能快速的找到一个完整的案例,使用这套函数呢?分析:在以上4个过程中,哪个最重要呢?找到它,去查看API文档!发现有对应的demo程序。将该demo导入到我们的程序中,运行,观察它的作用。

#include 
#include 
#include 
#include 

/*
预处理主要用于执行相同语句可以进行加速处理
大数据存储时也会进行使用 因为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 
#include 
#include 
#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 
#include 
#include 
#include 

#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

你可能感兴趣的:(Linux数据库学习,mysql,linux,数据库)