mysql的C API详解

于由astupidcoder发布

惭愧的是,折腾了这么久,一直都没有系统学过数据库知识,于是去慕课找了个数据库课程学了学,不得不说,我们的大学课程内容还是很陈旧啊,虽然基础一点没什么不好,但是作为现代的数据库教程,还在讲嵌入式SQL语言,什么EXEC SQL这样的C关键字~着实有点不靠谱。于是去网上找API介绍,发现大部分都是重用,就是说告诉你要写一个能跑起来的程序,需要哪些函数,但对于API缺乏整体介绍、出错处理等等介绍,导致雾里看花,偏偏我学什么都喜欢从基础学起,于是就去看了Mysql的API手册,先整理出C语言部分,其他语言等有空了慢慢梳理出来,但整体思路肯定都是差不多的。粘贴在这里,一方面是自己做个记录,一方面说不定哪个有缘人误入呢,哈哈。

C语言的数据库操作

所有内容都是参照Mysql手册:C-API;

概述

要用C语言API,首先得要安装一下开发者库,也就是除了apt install mysql-server以外,还要apt install libmysqlclient-dev,之后头文件和运行库才能装上。在编写调用mysql库的C语言时,需要包含mysql.h头文件。

要查看头文件和开发者库装到哪里去了(在编译的时候要用),可以使用以下命令:

root@linux:~# mysql_config --cflags
-I /usr/include/mysql
root@linux:~# mysql_config --libs
-L /usr/lib/x86_64-linux-gnu -lmysqlclient -lpthread -lz -lm -lrt -latomic -ldl

在编译时需要指明include文件夹和运行库文件夹时,也可以

root@linux:~# gcc -c `mysql_config --cflags` progname.c
root@linux:~# gcc -o progname progname.o `mysql_config --libs`

在用C语言操作数据库时,C客户端会自动申请缓存,从16kb到16MB封顶,也可以手动增加缓存上限。根据官方的说法,其实16MB的封顶值只是为了防止一些错误操作,而不是真的为了限制程序员。在查询一些大的数据,如blob、image等较大的数据时,客户端和服务端的缓存都需要足够大,一般客户端的缓存够,但是服务端的缓存只有1MB

You can increase this by changing the value of the max_allowed_packetparameter at server startup. See Section 5.1.1, “Configuring the Server”.

更新mysql版本后,可能造成旧的库与新server不兼容,最好重新编译之。

常用的数据结构

  • MYSQL

    相当于指针、文件号之类的东西,或者称之为不好理解的句柄。这用来指代一个MYSQL连接,并且几乎所有的函数都要使用它。官方不建议复制这个结构体,因为不能保证复制后的结构体有用。

  • MYSQL_RES

    用来指代一个返回元组的查询结果,比如select,show,describe,explain。之后的笔记用结果集来表示一次查询返回的结果。

  • MYSQL_ROW

    指向查询结果的某一行,这个结果是类型安全(原文是type-safe representation,大概意思是可以指代不同的数据类型)的,目前的实现是一个保存了字节长度的字节流。但不能将这个字节流看成是0结尾的字符串(null-terminated strings),因为有些二进制数据比如bolb和image可能包含null字节。

    MYSQL_ROW数据可以用 mysql_fetch_row()获得。

  • MYSQL_FIELD

    这个数据结构包含了表的某个属性的信息,包括属性名、类型、size等等,其实非常复杂的一个类。可以通过重复调用mysql_fetch_field()来取得MYSQL_FIELD数据。这个数据只包含属性信息,不是元组查询。下面介绍这个结构里包含了哪些玩意:

    • char * name

    显然是属性的名字了,一个null结尾字符串。如果查询语句中用了as,将会得到一个as后的名字。

    • char * org_name

    显然是属性的原名字,忽略AS。

    • char * table 和 char * org_table

    属于哪个表,差别同上。如果是视图的列,就返回视图名,如果是calculated fields(暂时不清楚是什么意思,猜测会不会是那种表达式或者avg函数计算出来的值),会得到空值。

    • char * db

    属于哪个数据库名,如果是calculated fields,会得到空值。

    • char * catalog

    不知道干嘛的,手册上说这个值永远是”def”

    • char * def

    这个属性的默认取值(即CREATE TABLE时用DEFAULT关键字指定的值),这个值必须使用 mysql_list_fields()来查询,其他函数不会填写这个值。

    • unsigned long length

    属性的size,相关函数在填写这个值时并不会先生成结果集,所以并不知道你存储的具体数据的size,而是说这个类型的size上限。

    • unsigned long max_length

    这是结果集中该属性的最大长度,是确实存在的数据的最大长度。这里的数据长度是指用字符串指代的数据的最大长度,比如-12.34,这个数字的长度应该是6。

    • unsigned int name_length, org_name_length, table_length, org_table_length, db_length, catalog_length, def_length

    明白地好像没啥好解释的

    • unsigned int flags

    又是位运算。用来指示这个域的一些状态,比如说是不是not null,是不是unique,是不是primiary key啊之类的。

    Flag Value Flag Description
    NOT_NULL_FLAG Field cannot be NULL
    PRI_KEY_FLAG Field is part of a primary key
    UNIQUE_KEY_FLAG Field is part of a unique key
    MULTIPLE_KEY_FLAG Field is part of a nonunique key
    UNSIGNED_FLAG Field has the UNSIGNED attribute
    ZEROFILL_FLAG Field has the ZEROFILL attribute
    BINARY_FLAG Field has the BINARY attribute
    AUTO_INCREMENT_FLAG Field has the AUTO_INCREMENT attribute
    ENUM_FLAG Field is an ENUM
    SET_FLAG Field is a SET
    BLOB_FLAG Field is a BLOBor TEXT (deprecated)
    TIMESTAMP_FLAG Field is a TIMESTAMP (deprecated)
    NUM_FLAG Field is numeric; see additional notes following table
    NO_DEFAULT_VALUE_FLAG Field has no default value; see additional notes following table

    可以用if (field->flags & NOT_NULL_FLAG)这样的语句来判断相关类型,也有几个宏IS_NOT_NULL(flags)和IS_PRI_KEY(flags),来快速完成判断。

    • unsigned int decimals

    小数点精度。

    • unsigned int charsetnr

    最是这行没搞懂,大概意思是说用一个ID号表示这个属性所采用的编码方式。但后面又说了一堆什么冲突规则,懒看。

    • enum enum_field_types type

    表示这一行到底是什么数据类型。具体如下:

    mysql-C-api-数据类型.png

  • MYSQL_FIELD_OFFSET

    指示当前阅读的是MYSQL filed list的第几行。从0开始计数。

  • my_ulonglong

    是一个数字类型,提供较大的数值范围。一些函数有时候会返回一个my_ulonglong的-1来表示出错,这时候可以将函数返回值与(my_ulonglong)-1作对比。在打印这个数值的时候,可能需要将其转换为ul,使用

    printf ("Number of rows: %lu\n",
          (unsigned long) mysql_num_rows(result));
    
  • my_bool

    布尔数。非零为真,零为假。

API函数概览

  • mysql_init()

    在需要线程安全的程序中初始化全局变量和线程句柄。

  • mysql_affected_rows()

    返回最近一次操作(insert,select,delete等)所影响的元组的数量。

  • mysql_autocommit()

    自动处理功能开关,不知道干嘛的

  • mysql_change_user()

    在一个已经打开的连接中更换用户和数据库

  • mysql_character_set_name()

    返回当前连接的默认字符集名

  • mysql_client_find_plugin()

    返回指向插件的指针,也不知道干嘛用的

  • mysql_client_register_plugin()

    注册一个插件

  • mysql_close()

    关闭一个服务器连接

  • mysql_commit()

    运行一个操作

  • mysql_connect()

    这个函数已经标记为废弃,建议使用mysql_real_connect()。函数的本来功能是连接一个服务端

  • mysql_creat_db()

    这个函数也废弃了,建议使用SQL的关键字CREATE DATABASE替代。

  • mysql_data_seek()

    在结果集中访问某个指定序号的元组。(支持随机访问)

  • mysql_debug()

    用给定字符串进行一个DBUG_PUSH。(不知道是什么意思)

  • mysql_drop_db()

    同废弃,建议使用SQL的关键字DROP DATABASE来替代。

  • mysql_dump_debug_info()

    要求服务端将debug信息写入日志

  • mysql_eof()

    已经废弃,建议使用mysql_errno()或者mysql_error()代替。函数本来的功能是查看结果集的最后一行元组是否已经被查看。

  • mysql_errno()

    返回最近一次调用的MYSQL函数出现的错误码

  • mysql_error()

    返回最近一次MYSQL函数调用出现的错误消息。

  • mysql_escape_string()

    类似于SQL关键字delimiter的功能,就是设置语句定界符

  • mysql_fetch_field()

    返回下一个属性的类型

  • mysql_fetch_field_direct()

    给定一个属性的序号,返回该属性的类型

  • mysql_fetch_fields()

    返回一个包含所有属性的数组

  • mysql_fetch_lengths()

    返回当前元组所有列的宽度,这是返回一个数组吗?也没说。

  • mysql_fetch_row()

    获取结果集的下一个元组

  • mysql_field_count()

    返回最近一次SQL查询所返回的属性的数量

  • mysql_field_seek()

    将列游标放到指定列

  • mysql_field_tell()

    返回最后一次mysql_fetch_field()函数中使用的列游标的位置

  • mysql_free_result()

    释放某个结果集使用的内存

  • mysql_get_character_set_info()

    返回当前字符集的信息

  • mysql_get_client_info()

    以字符串的形式返回当前mysql-client的版本号信息

  • mysql_get_client_version()

    以数字形式返回当前mysql_client的版本号信息

  • mysql_get_host_info()

    以字符串形式返回一个当前连接的信息

  • mysql_get_option()

    返回mysql_option()函数设置的option

  • mysql_get_proto_info()

    返回当前连接所使用的协议的版本号

  • mysql_get_server_info(), mysql_get_server_version()

    同上client

  • mysql_get_ssl_cipher()

    返回当前的SSL密码

  • mysql_hex_string()

    以十六进制形式编码一个字符串

  • mysql_info()

    返回最近一次查询的消息

  • mysql_init()

    生成或者初始化一个MYSQL结构体。

  • mysql_insert_id()

    返回上一次查询生成的AUTO_INCREMENT列ID。

    AUTO INCREMENT 字段,我们通常希望在每次插入新记录时,自动地创建主键字段的值。我们可以在表中创建一个 auto-increment 字段。比如我们在一个学生表中,希望每次插入数据时学号(主键)都自动递增1,可以用ALTER TABLE student AUTO_INCREMENT=init_num来设定初始值。

  • mysql_kill()

    干掉一个指定线程。好像是用于用pthread_kill()结束线程之前用来收回资源的。

  • mysql_library_end()

    结束一个mysql库,还不是很清楚怎么用。

  • mysql_library_init()

    初始化一个mysql库,同不知道有啥用。

  • mysql_list_dbs()

    返回匹配给定正则表达式的所有数据库的名字

  • mysql_list_fields()

    返回匹配给定正则表达式的所有属性的名字

  • mysql_list_processes()

    返回当前当前服务器的线程列表

  • mysql_list_tables()

    返回匹配给定正则表达式的所有表的名字

  • mysql_load_plugin(),mysql_load_plugin_v(),

    装载一个插件

  • mysql_more_results()

    检查是否还有更多结果

  • mysql_next_result()

    返回在多结果查询中的下一个结果

  • mysql_num_fields()

    返回某个结果集中的列数量

  • mysql_num_rows()

    返回某个结果集中的元组数量

  • mysql_options(),mysql_options4()

    为mysql_real_connect()设置各种连接参数

  • mysql_ping()

    查看与服务器的连接是否工作正常,如果有必要的话重新连接。

  • mysql_plugin_options()

    设置插件的选项

  • mysql_query()

    执行一个SQL查询。该查询命令应当以0结尾的字符串提供。

  • mysql_real_connect()

    连接一个SQL服务器

  • mysql_real_escape_string()

    和前面的mysql_escape_string()功能类似,但是这个函数在运行时考虑了当前的字符集。估计建议使用这个函数。

  • mysql_real_escape_string_quote()

    也差不多是设置定界符的,但是考虑了当前的字符集和引用环境。我也不知道具体干嘛的。

  • mysql_refresh()

    冲刷或充值表和缓存

  • mysql_reload()

    要求server重新载入指定表

  • mysql_reset_connection()

    重置一个连接,清空当前的对话状态

  • mysql_rollback()

    回滚一次操作

  • mysql_row_seek()

    令行游标在结果集中跳到某个指定偏移量,所需的参数由mysql_row_tell()函数提供。

  • mysql_row_tell()

    返回当前行游标位置。

  • mysql_select_db()

    选中某一个数据库

  • mysql_server_end()

    结束一个SQL C API库

  • mysql_server_init()

    初始化一个SQL C API库,这两个函数看起来对应mysql_library_end()和mysql_library_init()就是不知道有啥用。

  • mysql_session_track_get_first()

    获取当前对话状态改变信息的第一部分

  • mysql_session_track_get_next()

    获取当前对话状态改变信息的下一部分,还是不知道干吗用的。

  • mysql_set_character_set()

    为当前连接设置默认字符集

  • mysql_set_local_infile_default()

  • mysql_set_local_infile_handler()

    这两个函数都看不明白干嘛的。

  • mysql_set_server_option()

    为连接设置选项

  • mysql_sqlstate()

    返回上一次出错的查询的SLQSTATE错误码。

  • mysql_shutdown()

    关闭数据库服务

  • mysql_ssl_set()

    准备与服务器建立SSL连接。

  • mysql_stat()

    以字符串形式返回一个当前服务器的状态

  • mysql_store_result()

    一次性把整个结果集取回客户端。

  • mysql_thread_end()

    终结一个线程句柄,用于真正结束线程之前。

  • mysql_thread_init()

    初始化一个线程句柄,根据之前的解释,应当在线程创建之后立即调用。

  • mysql_thread_safe()

    如果客户端是线程安全的,则返回1

  • mysql_use_result()

    初始化一个结果集,用于支持一行接一行的取回。

  • mysql_warning_count()

    返回上一次SQL操作的警告数量。

典型的SQL程序调用流程

  1. 首先用mysql_library_init()初始化整个MYSQL数据库;

    这个函数mysql_init()会自动调用,因此在非多线程环境下忽略这个函数也不是不可以,但是这个函数并不线程安全,因此需要在开启多线程之前就解决调用mysql_library_init()来初始化数据库。

  2. 之后用mysql_init()初始化一个连接句柄,接着用mysql_real_connect连接到服务器;

    mysql_real_connect需要使用mysql_init()初始化得到的句柄。

  3. 之后进行SQL的具体操作,并处理操作的结果。

  • 这里可以用mysql_query()或者mysql_real_query(),两个函数的不同在于第一个需要一个0结尾的字符串,而第二个需要一个提供了长度的字符串,显然,当查询包含二进制数据(意味着可能包含null字符)时,必须使用后者。

  • 任何非select操作,都可以用mysql_affected_rows()来查看到底多少行受到了此次操作的影响。

  • 任何select操作类select操作(show,describe,explain),结果都以结果集的方式取回。

    DESCRIBE,经常简写成desc,用来给出表的各属性的信息;EXPLAIN,用于SELECT之前,可以解释SELECT运行的全过程,比如说表如何连接,操作先后顺序之类的。

  • select的结果可以用mysql_store_result()或者mysql_use_result()来获得,第一个函数一次性将整个结果集从服务器上拉下来保存到本地,而第二个一次一行地从服务器中获得结果,据手册说,第一个函数的使用率更高一些。

  • 无论是用mysql_store_result()或者mysql_use_result(),到最后都要用mysql_fetch_row()来完成每一行的读取,为了读取每一元组的每个属性的长度,可以使用mysql_fetch_lengths(),这个函数返回一个long的指针。

  • 用mysql_store_result()好的是不但可以顺序访问,还支持随机访问,可以用mysql_num_rows()来查看总共获得了几个元组的结果,之后用mysql_data_seek()ormysql_row_seek()来实现随机访问。但数据量过大的时候有可能溢出。

  • 用mysql_use_result的好处是处理速度可能更快一点,但不支持随机访问,还要担心服务器断开之类的。

  • 在任何的mysql_query()或者mysql_real_query()之后都可以使用mysql_store_result()或者mysql_use_result(),不用理会query的statement是不是select,如果不是,mysql_store_result()或者mysql_use_result()函数会失败,mysql_field_count()在这种情况下返回0。

  • 无论是mysql_store_result()或者mysql_use_result(),都可以通过重复调用mysql_fetch_field()来获得属性信息,或者通过mysql_fetch_field_direct()获取指定列数的结果,或者用mysql_fetch_fields()来一次性获取所有的列属性,再一个个读出来看。

  1. 使用mysql_close()关闭连接。

  2. 使用mysql_library_end()结束对MYSQL的使用。主要涉及到内存的释放。

  3. 在任何过程中出错,都可以使用mysql_errno() and mysql_error()函数来查找出错原因,一个返回错误码,一个返回错误字符串。返回的都是上一个调用的函数的出错情况。

常用函数详解

上面把所有函数都列出来了,但是详解肯定不能那么整,没啥用,没重点,还把人累死。不如就整理一些常用函数,需要啥特别功能的再去找。于是接下来按照上面的流程顺序来详解不同的函数。

在详解部分函数之前,看到函数一章里有个提示,就是大部分函数都返回指针或者整型数,返回指针的函数返回null指针表示出错,而返回整型数的函数返回0表示成功,返回非0表示失败,还特意强调,非0就是非0,不一定是-1等等,所以要用的话直接if (result)或最多if (result != 0)就行了,不要用if (result < 0)或者if (result = -1)等方式进行出错处理。除非特别标注,下面的函数返回值都遵从这个约定。

so here we go

  • int mysql_library_init(int argc, char **argv, char **groups)

    该函数要在所有的SQL API调用之前调用,如果用的是嵌入式的server,那么这个函数还会初始化一系列server的功能。

    在非多线程环境中,该函数可以不显式调用,因为mysql_init()会调用它,但是因为mysql_init可以用于线程环境,为每个线程创建各自的服务,而该函数则是线程不安全的,因此在多线程环境下,务必在任何多线程启动之前调用之。

    该函数的前两个参数类似main函数,argc是指argv的参数数量,argv就是一些对服务器进行设置(用于嵌入式server)的变量,groups也是对服务器进行设置,他指定了嵌入式服务器将要去读哪些字段的服务器设置(就是设置文件中的那些[mysql],[server]之类的字段。)

    在非嵌入式的情况下,一般的调用就是mysql_library_init(0, null, null),示例如下:

//##################
#include <mysql.h>
#include <stdlib.h>

int main(void) {
    if (mysql_library_init(0, NULL, NULL)) {
      fprintf(stderr, "could not initialize MySQL client library\n");
      exit(1);
    }

    /* Use any MySQL API functions here */

    mysql_library_end();

    return EXIT_SUCCESS;
  }

在嵌入式环境下的使用场景如下:

//##################
#include <mysql.h>
#include <stdlib.h>

static char *server_args[] = {
    "this_program",       /* this string is not used */
    "--datadir=.",
    "--key_buffer_size=32M"
  };
  static char *server_groups[] = {
    "embedded",
    "server",
    "this_program_SERVER",
    (char *)NULL
  };

  int main(void) {
    if (mysql_library_init(sizeof(server_args) / sizeof(char *),
                          server_args, server_groups)) {
      fprintf(stderr, "could not initialize MySQL client library\n");
      exit(1);
    }

    /* Use any MySQL API functions here */

    mysql_library_end();

    return EXIT_SUCCESS;
  }

  • MYSQL *mysql_init(MYSQL *mysql)

    调用完mysql_library_init之后,就要调用这个函数,生成一个句柄。这个函数可以用于线程,为每一个线程单独生成。生成的结构体将用于mysql_real_connect。

    当参数是null时,新生成一个句柄指针并将其返回,如果提供了一个MYSQL指针,函数就将它初始化,一样将它返回。不同的是,结构体是由该函数生成的,那么mysql_end()会将其主动释放,如果是用户提供的,那就需要用户手动释放。

  • 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)

    超级长,看的眼睛疼= =

    功能就是与一个server建立连接,任何SQL操作都必须等待该函数完全成功以后才可以进行。

    • 第一个参数就是mysql_init()生成的结构体,在调用mysql_real_connect之前,还可以用mysql_options来对连接进行一系列的设置。

    • host可以是一个IP地址,也可以是一个主机名。如果host是null或者localhost,将会连接本地server,windows下如果server支持共享内存就用共享内存,否则就用TCP/IP,在Unix下一般会使用管道连接,。也可以用mysql_options()来设定使用命名管道之类的。

    • user就是用户名了,如果用户名是null,那就假设是当前用户,如果在Unix下,就假设是当前登录用户,在Windows ODBC下,当前用户名必须被显式指定,可以参见Chapter 27, Connectors and APIs.

    • passwd就是密码了。也可以留null,这样的话必须mysql.user表里有该用户没有密码的一个表项,所有的权限都是根据这个表项来的,这可以和有密码的该用户有不同的权限。在调用该函数时不需要对密码进行加密,传输过程的密码加密由该函数自动完成。用户名和密码的字符集默认是Latin1,但是可以用mysql_option进行修改。

    • db就是要连接的数据库名。

    • port是端口号。如果不是0的话,那就会使用TCP/IP协议栈的端口号。

    • unix_socket如果非空,那就使用套接字或者命名管道进行连接。

    port和unix_socket是二选一的关系,到底是用port还是unix_socket,这个是由HOST参数决定的。

    • client_flag一般都是0,但是也可以用一些奇怪的组合来要求server表现出特定的动作。

    上面这些参数可以在调用时显示提供,也可以从一个option file读入,要从option file读入的话,需要调用mysql_options(),用MYSQL_READ_DEFAULT_FILE或者MYSQL_READ_DEFAULT_GROUP参数来指定option file的路径。从这个文件中读取的参数将用来填写那些在该函数调用时留空的字段。

    在当前版本的API中,reconnect flag(MYSQL结构体的一个字段)被设置为0,意味着当丢失连接的时候,不会主动重新连接,可以用mysql_options()来重设这个字段,设为1表示会主动重连。

  • int mysql_real_query(MYSQL *mysql, const char *stmt_str, unsigned long length)

  • int mysql_query(MYSQL *mysql, const char *stmt_str)

    这两个函数都用来在MYSQL指定的连接上执行stmt_str指定的SQL指令,区别是,第一个函数的stmt_str不是0结尾的字符串,需要length来提供长度,这在一些SQL指令中包含二进制的情况中比较好用,甚至是必须的。根据手册,第一个函数的执行速度也更快,因为不需要调用strlen

    可以使用mysql_field_count()查看这两个函数是否返回了一个结果集。

  • my_ulonglong mysql_affected_rows(MYSQL *mysql)

    该函数一般在执行了上面两个query函数后立即调用,返回值为上面两个query函数的操作到底影响了多少行(update、delete、insert)等,对于SELECT操作,query函数的行为和mysql_num_rows()一致。

    这个函数返回-1表示出错,可以用(my_ulonglong)-1来检查。

  • my_ulonglong mysql_num_rows(MYSQL_RES *result)

    返回结果集的行数。如果使用的是mysql_store_result(),将直接返回正确的数字,而使用mysql_use_results()的话,直到所有的结果取回之前,该函数的返回值都不正确。

  • MYSQL_RES *mysql_store_result(MYSQL *mysql)

  • MYSQL_RES *mysql_use_result(MYSQL *mysql)

    在调用query函数之后,如果query了一个SELECT、SHOW、DESCRIPT、EXPLAIN、CHECK_TABLE等产生一个结果集的操作,那么就可以用这两个函数来获取结果,即使是在不产生结果集的query之后,调用这两个函数也不会产生副作用。

    在开启多SQL语句支持后(就是一次query可以携带多个SQL语句),应该循环调用mysql_next_result()来判别是否有更多的结果,对每一个结果都可以使用这个两个函数来获得结果集。

    这两个函数都会申请一块内存用来存储结果,因此在结果使用完毕后,应该使用mysql_free_result()来释放申请的内存,不然就等着C语言的最大坑——内存泄露吧。哇哈哈……我特么在高兴什么呢,傻逼。

    在使用完这两个函数之后,都应当使用mysql_fetch_row()来获取一个个的结果。

    mysql_store_result()可以一次性取回所有的结果,他返回null时,有可能是结果集本身为空,也有可能是出错了,因此需要用错误处理函数来鉴别到底是哪一种情况。还可以使用mysql_row_seek()或者mysql_row_tell()在结果集里进行随机访问。

    mysql_use_result()之后从服务端一次取回一个结果,不能进行随机访问,而且在你把结果集中的所有结果取回并处理完之前,任何其他线程发起的任何针对正在读取数据的表格的query命令都无法执行。而且如果你一次没取完就使用了新的SELECT语句,那么没有被取回的结果将作为下一次query返回的结果集的一部分返回。

    由此观之,mysql_use_result()除了稍微快一些,不占内存一些,实在是没有任何可取之处啊。

  • MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)

    从结果集中取出下一个结果。当取完之后返回null,如果用了mysql_use_result(),出错时也会返回null。

    一个结果当然就相当于一个元组,这个元组到底有几个属性要靠mysql_num_fields(result)来获得,假设row表示一个结果元组,那么row[0]到row[mysql_num_fields(result)-1]的值分别是指向那些具体值的指针,null指针表示数据库中的NULL值。

    该结果元组的每个值在这里都被认为是一个字符串(或说比特流),但不能当成0结尾的字符串,因为可能有二进制数据。因为不能认为这些数据串是0结尾的字符串,因此想知道这些字符串的长度,必须使用mysql_fetch_lengths来获取,这个函数将得到一个无符号长整型数的指针。

  • unsigned int mysql_num_fields(MYSQL_RES *result)

  • unsigned int mysql_field_count(MYSQL *mysql)

    这两个函数的功能基本上是等价的,只是传递的参数类型不相同。返回当前结果集中的属性数量,即每一个元组包含的值的数量。因为有时候mysql_store_result()或者mysql_use_result()返回一个null指针,此时你就没有MYSQL_RES指针可以用,这时候可以使用mysql_field_count(),返回0的话说明这次query不是select。

  • unsigned long *mysql_fetch_lengths(MYSQL_RES *result)

    得到当前元组的每一个值的长度。对于null值(mysql_fetch_row()返回一个null指针)或者空值(mysql_fetch_row()返回一个非空指针,但实际上列是空值,也就是没有插入数据)。

  • MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result)

    这个函数返回一个MYSQL_FIELD结构的数组,这个结构体上面详解过了,可以得到每一个属性的详细信息。感觉虽然看起来很牛逼,但是对于一个业务层面的应用来说用处不是很大,特别是就自己的应用对应自己的数据库的时候。

    与这个函数功能类似的函数还有mysql_fetch_field()和mysql_fetch_field_direct()。

  • MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result)

    一次一个地获取结果集的属性信息,第一次调用返回第一个,第二次返回第二个,全部调用完毕之后返回null。

  • MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *result, unsigned int fieldnr)

    这个函数直接按属性的位置来获取属性信息。显然是mysql_num_fields()或mysql_field_count()获取列数之后再用的。

  • void mysql_close(MYSQL *mysql)

    当用完了之后,就关闭连接。如果是MYSQL的结构设计用mysql_init()或者mysql_connect()生成的,此时还会释放句柄所占用的内存。

  • void mysql_library_end(void)

    结束对整个库的使用,用这个函数主要是避免内存泄露。

一个综合示例程序

//##################
#include<stdio.h>
#include<mysql.h>
#include<stdlib.h>
#include<string.h>

void query_and_check(MYSQL *Handler,char *stmt_str,int len);
//这个函数用来输出query的结果
void print_value_type(MYSQL_RES *,int filed_num);
//这个函数用来输出结果集的属性类型

int main(){

    MYSQL *Handler;
    const char *host      = "localhost";
    const char *user      = "nbt";
    const char *passwd  = "nbt123";
    const char *db         = "test";
    unsigned int port = 3306;

    if (mysql_library_init(0,NULL,NULL)){
        printf("数据库库初始化出错\n");
        exit(1);
    }else{
        printf("数据库库初始化成功\n");
    }

    if ( (Handler = mysql_init(NULL)) == NULL ){
        printf("数据库初始化错误\n");
        exit(1);
    }else{
        printf("数据库初始化成功\n");
    }

    if ( (Handler = mysql_real_connect(Handler,host,user,passwd,db,port,NULL,0)) == NULL ){
        printf("连接数据库失败\n");
        exit(1);
    }else{
        printf("数据库连接成功\n");
    }

    //设置默认字符集,不设置的话据说客户端和服务端的通信默认是LATIN1,不支持中文。
    if (!mysql_set_character_set(Handler, "utf8")){
    printf("客户端编码已设置为: %s\n",
           mysql_character_set_name(Handler));
    }

    char stmt_str[512];   //用来存储SQL命令
    do{
        printf("请输入SQL命令\n");
        if ( fgets(stmt_str,512,stdin) ){
            int len = strlen(stmt_str);
            if (stmt_str[len-1] = '\n'){
                stmt_str[len-1] = 0;
                --len;
            }
            query_and_check(Handler,stmt_str,len);
        }else break;
    }while(1);

    mysql_close(Handler);
    mysql_library_end();

    return 0;

}

void query_and_check(MYSQL *Handler,char *stmt_str,int len){

    if (mysql_real_query(Handler,stmt_str,len)){
        printf("操作\"%s\"出错\n",stmt_str);
        printf("%s\n",mysql_error(Handler));
        //exit(1);
    }else{
        MYSQL_RES *result = mysql_store_result(Handler);
        if (result){
            //说明一个类SELECT语句查询成功并返回了结果
            int row_num   = mysql_num_rows(result);//查询返回的元组数量
            int field_num = mysql_num_fields(result);//查询返回的
            MYSQL_ROW row;
            int row_read = 0;
            print_value_type(result,field_num);
            if (row_num == 0)
                printf("查询结果为空集\n");
            while ( (row = mysql_fetch_row(result) ) != NULL ){
                ++row_read;
                unsigned long *lengths = mysql_fetch_lengths(result); //获得该元组每一个值的长度
                for (int i = 0 ; i != field_num ; ++i){
                    if ( lengths[i] == 0 ){ //null值或者空值
                        if (row[i] == NULL)
                            printf("NULL   ");
                        else printf("空值   ");
                    }else printf("[%.*s] ", (int) lengths[i],row[i]   );
                }
                printf("\n");
            }
            if (row_read != row_num )
                printf("未读完结果集的所有元组,出现错误:%s",mysql_error(Handler));
            mysql_free_result(result);  //记得释放内存
        }else{
            unsigned int field_num = mysql_field_count(Handler);
            if( field_num == 0 ){
            //说明此次query不是查询指令
                my_ulonglong aff_rows = mysql_affected_rows(Handler);
                printf("操作\"%s\"成功,共修改>>%lu<<行\n",stmt_str,(unsigned long)aff_rows);
            } else { //是查询任务,但是没有返回值,说明出了莫名其妙的错
                printf("%s\n",mysql_error(Handler));
            }
        }
    }
}

void print_value_type(MYSQL_RES *result,int field_num){

    for(int i = 0 ; i != field_num ; ++i){
        MYSQL_FIELD *fi = mysql_fetch_field(result);
        switch (fi->type){
            case MYSQL_TYPE_TINY:
               printf("tinyint  ");
               break;
            case MYSQL_TYPE_SHORT:
               printf("shortint  ");
               break;
            case MYSQL_TYPE_LONG:
               printf("integer   ");
               break;
            case MYSQL_TYPE_INT24:
               printf("mediumint ");
               break;
            case MYSQL_TYPE_LONGLONG:
               printf("bigint   ");
               break;
            case MYSQL_TYPE_DECIMAL:
               printf("分数   ");
               break;
            case MYSQL_TYPE_NEWDECIMAL:
               printf("精确分数 ");
               break;
            case MYSQL_TYPE_FLOAT:
               printf("FLOAT    ");
               break;
            case MYSQL_TYPE_DOUBLE:
               printf("DOUBLE   ");
               break;
            case MYSQL_TYPE_BIT:
               printf("BIT  ");
               break;
            case MYSQL_TYPE_TIMESTAMP:
               printf("时间戳  ");
               break;
            case MYSQL_TYPE_DATE:
               printf("日期  ");
               break;
            case MYSQL_TYPE_TIME:
               printf("时间  ");
               break;
            case MYSQL_TYPE_DATETIME:
               printf("日期时间  ");
               break;
            case MYSQL_TYPE_YEAR:
               printf("年份  ");
               break;
            case MYSQL_TYPE_STRING:
               printf("STRING  ");
               break;
            case MYSQL_TYPE_VAR_STRING:
               printf("VAR_STRING   ");
               break;
            case MYSQL_TYPE_BLOB:
               printf("文本  ");
               break;
            case MYSQL_TYPE_SET:
               printf("集合  ");
               break;
            case MYSQL_TYPE_ENUM:
               printf("枚举类型  ");
               break;
            case MYSQL_TYPE_GEOMETRY:
               printf("不知道  ");
               break;
            case MYSQL_TYPE_NULL:
               printf("NULL  ");
               break;
            default:
               printf("what happened?");
               break;
        }
    }
    printf("\n");
}

编译如下:

gcc `mysql_config --cflags` test_c_api.c -o axiba `mysql_config --libs`

连接时需要的用户名什么的自定义即可。运行效果可以自行测试。


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据。