Linux下的c/c++开发之操作mysql数据库

libmysqlclient-dev介绍(Linux 下的 Mysql C/C++ 开发包)

libmysqlclient-dev 是一个开发包,在Linux环境下为使用 MySQL C API 进行开发的c/c++程序员提供头文件(如 mysql.h)和静态库/动态库的链接信息(如 libmysqlclient.so)。

它是 MySQL 客户端库 libmysqlclient 的开发版本,不提供运行时功能,而是用于编译和构建你的程序。

libmysqlclient-dev包含的内容:

类别 路径或文件名 说明
头文件 /usr/include/mysql/mysql.h 主头文件,声明了所有 MySQL C API 接口函数,如 mysql_init()
/usr/include/mysql/mysql_version.h 定义 MySQL 的版本号和宏,如 MYSQL_VERSION_ID
/usr/include/mysql/my_list.h 定义链表结构,主要用于 MySQL 内部实现
动态库 /usr/lib/x86_64-linux-gnu/libmysqlclient.so 动态链接库,编译时使用 -lmysqlclient 链接,运行时动态加载
静态库 /usr/lib/x86_64-linux-gnu/libmysqlclient.a 静态链接库,将库打包进可执行文件中(较少使用)
搜索路径 /usr/include/mysql/ MySQL C API 所有头文件所在目录,编译时通常用 -I 指定
符号链接 /usr/lib/x86_64-linux-gnu/libmysqlclient.so -> libmysqlclient.so.X.Y.Z 指向具体版本库文件的符号链接,供 -lmysqlclient 查找使用

libmysqlclient-dev的安装

bash 复制代码
sudo apt install libmysqlclient-dev

安装成功后的简单示例:

test_mysql.c

cpp 复制代码
#include <mysql/mysql.h>
#include <stdio.h>

int main() {
    MYSQL* conn = mysql_init(NULL);
    if (!conn) {
        printf("Init failed\n");
        return 1;
    }
    printf("libmysqlclient 初始化成功\n");
    mysql_close(conn);
    return 0;
}

编译:

bash 复制代码
gcc test_mysql.c -o test_mysql -lmysqlclient

<mysql.h>介绍

<mysql.h> 是**MySQL C API(libmysqlclient)**的主头文件,它提供了访问 MySQL 数据库服务器所需的全部函数、宏、结构体和常量。

它是 C/C++ 中与 MySQL 数据库交互的底层接口,你可以用它完成:

  • 建立数据库连接

  • 执行 SQL 查询

  • 获取查询结果

  • 处理错误

  • 管理事务等

<mysql.h>中的结构体

常用结构体简介

结构体名 类型 作用描述 使用场景举例
MYSQL 连接对象 表示一个与数据库的连接句柄,初始化、连接、关闭等操作都依赖它 mysql_initmysql_real_connect → 查询/断开
MYSQL_RES 结果集对象 表示一次查询后的结果集(SELECT 返回的所有数据) mysql_store_result / mysql_use_result 得到的结果集
MYSQL_ROW 行数据 表示查询结果中的一行,每一列是一个 char*,组成一个 char** 遍历结果集时使用 mysql_fetch_row 得到每一行
MYSQL_FIELD 字段描述 表示表结构中一列的元信息(列名、类型、长度等) 查询字段信息用 mysql_fetch_fields() 获取数组
MYSQL_STMT 预处理语句句柄 表示一条准备执行的 SQL 预处理语句(prepared statement) 用于 mysql_stmt_prepare, mysql_stmt_execute 等流程
MYSQL_BIND 参数绑定对象 用于绑定预处理语句的输入参数或输出结果列 配合 mysql_stmt_bind_param / mysql_stmt_bind_result 使用
MYSQL_TIME 时间结构体 表示日期和时间,适用于绑定时间类型字段 MYSQL_BIND 中处理 DATETIME / DATE 等类型字段

MYSQL

MYSQL 表示一个数据库连接句柄。它包含了与数据库交互的所有必要信息,包括连接参数、状态、通信通道等。通过 MYSQL 结构体,开发者可以管理数据库连接、执行查询、获取结果等。

MYSQL结构体的定义

cpp 复制代码
typedef struct MYSQL {
  NET net;                       // 通信参数
  unsigned char *connector_fd;  // 用于 SSL 的连接器文件描述符
  char *host;                   // 主机名
  char *user;                   // 用户名
  char *passwd;                 // 密码
  char *unix_socket;            // Unix 域套接字路径
  char *server_version;         // 服务器版本
  char *host_info;              // 主机信息
  char *info;                   // 执行信息
  char *db;                     // 当前选择的数据库
  struct CHARSET_INFO *charset;// 字符集信息
  MYSQL_FIELD *fields;          // 字段数组指针
  struct MEM_ROOT *field_alloc;// 用于字段分配的内存池
  uint64_t affected_rows;       // 最近一次操作影响的行数
  uint64_t insert_id;           // 插入操作返回的自增ID
  uint64_t extra_info;          // 未使用的扩展信息
  unsigned long thread_id;      // 服务器端的线程ID
  unsigned long packet_length;  // 包长度
  unsigned int port;            // 服务器端口
  unsigned long client_flag;    // 客户端标志
  unsigned long server_capabilities; // 服务器能力标志
  unsigned int protocol_version;     // 协议版本
  unsigned int field_count;     // 字段数量
  unsigned int server_status;   // 服务器状态
  unsigned int server_language; // 服务器使用的语言
  unsigned int warning_count;   // 警告数量
  struct st_mysql_options options;   // 客户端连接选项
  enum mysql_status status;          // 当前连接状态
  enum enum_resultset_metadata resultset_metadata; // 结果集元数据状态
  bool free_me;                // 是否在 mysql_close 时释放
  bool reconnect;              // 是否自动重连

  char scramble[SCRAMBLE_LENGTH + 1]; // 会话级随机字符串

  LIST *stmts;                      // 所有预处理语句的链表
  const struct MYSQL_METHODS *methods; // 连接使用的方法集合
  void *thd;                        // 线程句柄(MySQL 内部线程上下文)

  bool *unbuffered_fetch_owner;    // 指向 MYSQL_RES 或 MYSQL_STMT 中的标志位,用于取消未缓冲结果集
  void *extension;                 // 扩展字段,供插件或其他扩展使用
} MYSQL;

示例:

cpp 复制代码
int main() {
    MYSQL *conn = mysql_init(nullptr);  // 初始化 MYSQL 结构体
    // 建立连接(参数:host, user, password, db, port, unix_socket, client_flag)
    if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, nullptr, 0)) {
        std::cerr << "Connection failed: " << mysql_error(conn) << std::endl;
        return 1;
    }
}

此时的conn

bash 复制代码
| conn字段             描述                                              
| ------------------ | ----------------------------------------------- 
| `host`             | `localhost`:连接的主机名或 IP。                         
| `user`             | `root`:连接使用的用户名。                               
| `passwd`           | `123456`:连接使用的密码。                               
| `unix_socket`      | `nullptr`:Unix socket 文件路径(仅在使用 socket 连接时有值)。  
| `port`             | `3306`:连接的端口号。                                  
| `protocol_version` | `10`:MySQL 协议版本。                                

MYSQL_TIME

MYSQL_TIME 结构体在 MySQL 客户端库中用于表示日期和时间数据。它包含了日期、时间、时区等信息,常用于存储和处理 MySQL 数据库中涉及日期、时间、时间戳等类型的数据。

MYSQL_TIME结构体的定义

cpp 复制代码
typedef struct MYSQL_TIME {
  unsigned int year;      // 年份
  unsigned int month;     // 月份(1-12)
  unsigned int day;       // 日(1-31)
  unsigned int hour;      // 小时(0-23)
  unsigned int minute;    // 分钟(0-59)
  unsigned int second;    // 秒数(0-59)
  unsigned long second_part; // 微秒部分(0-999999)
  bool neg;               // 是否为负时间(仅限 TIME 类型)
  enum enum_mysql_timestamp_type time_type; // 时间类型(如 DATE、DATETIME、TIME)
  int time_zone_displacement; // 时区偏移(单位:秒)
} MYSQL_TIME;

MYSQL_FIELD

MYSQL_FIELD 结构体是 MySQL 客户端库中用于描述数据库中表的字段(列)的元数据结构。它包含了关于数据库表列的详细信息,如列的名称、数据类型、长度、是否允许为 NULL 等。这个结构体是执行查询时,检索列信息的关键部分。

MYSQL_RES结构体的定义

cpp 复制代码
typedef struct MYSQL_FIELD {
  char *name;               // 列名
  char *org_name;           // 原始列名(如果使用了别名)
  char *table;              // 所在表名(如果该列来源于表字段)
  char *org_table;          // 原始表名(如果使用了表别名)
  char *db;                 // 所在数据库名
  char *catalog;            // 所在目录名(一般为 "def")
  char *def;                // 默认值(由 mysql_list_fields 设置)
  unsigned long length;     // 列宽(创建时指定的长度)
  unsigned long max_length; // 当前查询结果中该字段的最大长度
  unsigned int name_length;       // 列名的长度
  unsigned int org_name_length;   // 原始列名的长度
  unsigned int table_length;      // 表名的长度
  unsigned int org_table_length;  // 原始表名的长度
  unsigned int db_length;         // 数据库名的长度
  unsigned int catalog_length;    // 目录名的长度
  unsigned int def_length;        // 默认值的长度
  unsigned int flags;         // 字段标志(如是否为主键、自增等)
  unsigned int decimals;      // 小数位数(针对浮点数类型)
  unsigned int charsetnr;     // 使用的字符集编号
  enum enum_field_types type; // 字段类型(定义在 mysql_com.h 中)
  void *extension;            // 扩展字段(用于内部用途)
} MYSQL_FIELD;

假设我们有一个实际的数据库表,名为 users,它包含如下字段:

cpp 复制代码
| id | name  | age | created\_at         |
| -- | ----- | --- | ------------------- |
| 1  | Alice | 30  | 2025-05-01 10:00:00 |
| 2  | Bob   | 25  | 2025-05-02 14:30:00 |

MYSQL_FIELD 结构体存储的内容:

cpp 复制代码
| `MYSQL_FIELD` 字段 | 描述                               
| ----------------   | -------------------------------- 
| `name`             | "id"                             
| `org_name`         | "id"                            
| `table`            | "users"                          
| `org_table`        | "users"                          
| `db`               | "my\_database"                   
| `length`           | 11 (表示列宽度,对于整数类型可以是字符宽度)         
| `max_length`       | 11 (最大值,可能是查询返回时的最大宽度)           
| `type`             | `MYSQL_TYPE_LONG` (表示该列数据类型是长整型) 
| `flags`            | `NOT_NULL` (表明该列不允许为 `NULL`)     
| `decimals`         | 0                                
| `charsetnr`        | `CHARSET_UTF8` (表示字符集)           

MYSQL_RES

用于表示查询结果的一个数据结构。它包含了从数据库中查询得到的所有结果集数据。在执行查询操作时,MySQL 会返回一个 MYSQL_RES 对象,里面保存了查询的字段信息、行数据以及与结果相关的其他信息。通过 MYSQL_RES 结构体,我们可以访问到结果集的元数据(如列信息)以及具体的行数据。

MYSQL_RES结构体的定义

cpp 复制代码
typedef struct MYSQL_RES {
  uint64_t row_count;                  // 查询结果中的总行数
  MYSQL_FIELD *fields;                // 指向字段(列)描述信息数组
  struct MYSQL_DATA *data;           // 实际的查询结果数据
  MYSQL_ROWS *data_cursor;           // 当前读取到的结果行指针
  unsigned long *lengths;            // 当前行中每个字段的长度数组
  MYSQL *handle;                     // 对应的 MYSQL 连接句柄,用于非缓冲读取
  const struct MYSQL_METHODS *methods; // 内部使用的方法表指针
  MYSQL_ROW row;                     // 当前读取的行数据(非缓冲模式)
  MYSQL_ROW current_row;             // 当前行缓冲区(缓冲模式)
  struct MEM_ROOT *field_alloc;     // 用于字段描述信息的内存分配器
  unsigned int field_count, current_field; // 字段总数,当前字段索引
  bool eof;                          // 指示是否已到达结果集末尾(用于 mysql_fetch_row)
  bool unbuffered_fetch_cancelled;  // 标记是否被 mysql_stmt_close 取消
  enum enum_resultset_metadata metadata; // 结果集的元数据类型
  void *extension;                   // 扩展字段(MySQL 内部使用)
} MYSQL_RES;

假设我们有一个名为 users 的数据库表,结构如下:

cpp 复制代码
| id | name  | age | created\_at         |
| -- | ----- | --- | ------------------- |
| 1  | Alice | 30  | 2025-05-01 10:00:00 |
| 2  | Bob   | 25  | 2025-05-02 14:30:00 |

假设我们执行的 SQL 查询:

cpp 复制代码
SELECT id, name, age, created_at FROM users;

查询结果如下:

cpp 复制代码
| id | name  | age | created\_at         |
| -- | ----- | --- | ------------------- |
| 1  | Alice | 30  | 2025-05-01 10:00:00 |
| 2  | Bob   | 25  | 2025-05-02 14:30:00 |

MYSQL_RES 结构体存储的内容:

cpp 复制代码
| `MYSQL_RES` 字段             | 描述                                                                                    |
| ---------------------------- | ----------------------------------------------------------
| `row_count`                  | 2 (表示查询结果中的行数,这里有 2 行数据)                                                              

| `fields`                     | 指向一个包含 4 个 `MYSQL_FIELD` 结构体的数组,分别表示 `id`、      
                                 `name`、`age` 和 `created_at` 四个字段。   
        
| `data_cursor`                | 用于遍历查询结果的指针,指向当前行数据。                                                                  

| `lengths`                    | 指向一个数组,存储每一列数据的长度。例如:`[11, 50, 3, 19]`, 
                                 分别表示 `id`、`name`、`age` 和 `created_at`字段的字符长度。
 
| `handle`                     | 指向当前 MySQL 连接的句柄,用于标识与 MySQL 数据库的连接。                                                  

| `methods`                    | 指向包含处理查询结果方法的结构体,如 `fetch_row`。                                                       

| `row`                        | 当使用非缓冲查询时,指向当前行数据。                                                                    

| `current_row`                | 用于存储当前读取的行数据。                                                                         

| `field_count`                | 表示查询结果中字段的数量,这里是 4。                                                                   

| `eof`                        | 表示查询结果是否已经读取到末尾。当所有行数据都被读取后`true`。                                                  

| `unbuffered_fetch_cancelled` | 如果查询是未缓冲查询且结果已被取消,则为 `true`。                                                          

| `metadata`                   | 用于存储查询结果集的元数据。                                                                        

MYSQL_BIND

MYSQL_BIND 结构体主要用于处理 MySQL 准备语句(Prepared Statements)的输入参数和输出结果的绑定。通过使用 MYSQL_BIND,你可以将输入参数传递给 MySQL 并从数据库中获取查询结果。它在与 MYSQL_STMT 结构体结合时,提供了参数绑定功能,用于执行具有参数化查询的 SQL 语句。

作用:

  • 输入参数绑定 :当执行 INSERTUPDATE 等操作时,MYSQL_BIND 结构体可以将 C 语言的变量与 SQL 语句的参数进行绑定。你将一个 C 语言的变量传递给 MySQL,MySQL 执行查询时会使用这个变量的值。

  • 输出结果绑定 :当执行 SELECT 查询时,查询结果的列可以通过 MYSQL_BIND 结构体与 C 语言中的变量绑定,将查询结果存储到相应的变量中。

MYSQL_BIND结构体的定义

cpp 复制代码
typedef struct MYSQL_BIND {
  unsigned long *length; // 指向输出数据长度的指针(用于接收时获取实际长度)
  bool *is_null;         // 指向是否为 NULL 的标志指针
  void *buffer;          // 实际存放或读取数据的缓冲区指针
  bool *error;           // 如果在取数据时发生截断,用于记录是否出错的标志指针
  unsigned char *row_ptr; // 当前数据在行缓冲区中的位置指针
  void (*store_param_func)(NET *net, struct MYSQL_BIND *param); // 发送参数到服务器时使用的函数指针
  void (*fetch_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row); // 从服务器读取结果时的处理函数
  void (*skip_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row);  // 跳过结果时的处理函数
  unsigned long buffer_length;       // buffer 的长度(取字符串或二进制类型时必须设置)
  unsigned long offset;              // 用于字符串或二进制类型的偏移读取位置
  unsigned long length_value;        // 如果 length 指针为空,则使用此值表示长度
  unsigned int param_number;         // 参数编号(用于错误消息或统计)
  unsigned int pack_length;          // 内部使用的打包数据长度
  enum enum_field_types buffer_type; // 缓冲区中数据的类型(如 MYSQL_TYPE_STRING 等)
  bool error_value;                  // 当 error 为 NULL 时使用此字段表示错误
  bool is_unsigned;                  // 若为无符号整数类型,则此字段为 true
  bool long_data_used;               // 如果调用过 mysql_send_long_data,则此字段为 true
  bool is_null_value;                // 当 is_null 为 NULL 时使用此字段表示是否为 NULL
  void *extension;                   // 扩展字段,供 MySQL 内部使用
} MYSQL_BIND;

输入参数绑定示例:

在这个例子中,我们将数据插入到一个名为 users 的表中。假设 users 表有两个字段:idname。我们通过 MYSQL_BIND 结构体绑定输入参数。

cpp 复制代码
| id | name  |
| -- | ----- |
| 1  | Alice |
cpp 复制代码
int main() {
    MYSQL *conn;
    MYSQL_STMT *stmt;
    MYSQL_BIND bind[2];
    // 假设已经连接到数据库
    // 准备 SQL 查询语句
    const char *query = "INSERT INTO users (id, name) VALUES (?, ?)";

    // 初始化 MYSQL_STMT
    stmt = mysql_stmt_init(conn);

    // 设置 id 和 name 的值
    unsigned long id = 2;
    char name[50] = "Bob";

    // 清空绑定数组
    memset(bind, 0, sizeof(bind));

    // 绑定 id 参数
    bind[0].buffer_type = MYSQL_TYPE_LONG; // 类型是 LONG
    bind[0].buffer = (char *)&id;          // 绑定值
    bind[0].is_null = 0;                   // 表示参数非 NULL

    // 绑定 name 参数
    bind[1].buffer_type = MYSQL_TYPE_STRING; // 类型是 STRING
    bind[1].buffer = (char *)name;           // 绑定值
    bind[1].buffer_length = sizeof(name);    // 字符串长度
    bind[1].is_null = 0;                    // 表示参数非 NULL

    // 将绑定的参数应用到语句中
    mysql_stmt_bind_param(stmt, bind);

    ..........
    return 0;
}

输出参数绑定示例:

在这个例子中,我们从名为 users 的表中查询数据。假设 users 表有两个字段:idname。我们将通过 MYSQL_BIND 结构体绑定输出参数,并从查询结果中获取数据。

cpp 复制代码
| id | name  |
| -- | ----- |
| 1  | Alice |
cpp 复制代码
int main() {
    MYSQL *conn;
    MYSQL_STMT *stmt;
    MYSQL_BIND bind[2]; // 绑定输出参数

    // 假设已经连接到数据库
    // 准备 SQL 查询语句
    const char *query = "SELECT id, name FROM users WHERE id = 1";

    // 初始化 MYSQL_STMT
    stmt = mysql_stmt_init(conn);

    // 设置输出变量
    unsigned long output_id;
    char output_name[50];

    // 清空绑定数组
    memset(bind, 0, sizeof(bind));

    // 绑定输出参数 id
    bind[0].buffer_type = MYSQL_TYPE_LONG;  // 类型是 LONG
    bind[0].buffer = (char *)&output_id;    // 输出的 id 参数
    bind[0].is_null = 0;                    // 非 NULL
    bind[0].length = 0;

    // 绑定输出参数 name
    bind[1].buffer_type = MYSQL_TYPE_STRING; // 类型是 STRING
    bind[1].buffer = (char *)output_name;    // 输出的 name 字符串
    bind[1].buffer_length = sizeof(output_name); // 输出缓冲区长度
    bind[1].is_null = 0;                    // 非 NULL
    bind[1].length = 0;

    // 将绑定的输出参数应用到语句中
    mysql_stmt_bind_result(stmt, bind); // 绑定输出参数

    // 执行查询
    mysql_stmt_execute(stmt);

    return 0;
}

此时bind[2]的结果是:

cpp 复制代码
  bind[0]                      | 描述                                                        |
| ---------------------------- | --------------------------------------------------------- 

| `buffer_type`                | `MYSQL_TYPE_LONG` (表示 `id` 字段的数据类型是 `LONG`,即整数 
                                  类型)         

| `buffer`                     | `&output_id` (指向存储查询结果的变量的指针,`output_id` 存储 
                                  查询结果中的 `id` 值) 

| `is_null`                    | `0`                                         

| `length`                     | `0`                             



  bind[1]                      | 描述                                                        |
| ---------------------------- | --------------------------------------------------------

| `buffer_type`                | `MYSQL_TYPE_LONG` (表示 `id` 字段的数据类型是 `LONG`,即整 
                                 数类型)    
     
| `buffer`                     | `&output_id` (指向存储查询结果的变量的指针,`output_id` 存 
                                 储查询结果中的 `id` 值) 

| `is_null`                    | `0`                                           

| `length`                     | `0`                                 

| `buffer_length`              | `0`                                                        

MYSQL_STMT

MYSQL_STMT 是一个结构体,用来操作预处理 SQL 语句()。你可以把它理解成一个"SQL 执行容器",你把 SQL 丢进去,把参数绑上去,它帮你执行完,还能帮你取出结果。

举个最直白的比喻:

想象你开了一家饭店,来了条订单:

cpp 复制代码
SELECT name, age FROM users WHERE id = ?

这时候:

  • MYSQL_STMT 就像是你厨房里的一口锅

    • 你先用 mysql_stmt_prepare() 把 SQL(食谱)放进去,这叫"准备锅";

    • 然后你往锅里加料(mysql_stmt_bind_param() 把参数比如 id=100 填进去);

    • 再点火(mysql_stmt_execute() 执行);

    • 然后把煮出来的东西(查询结果)装盘(mysql_stmt_bind_result()mysql_stmt_fetch());

    • 用完后洗锅(mysql_stmt_close())。

它具体做了哪些事?

操作名称 简单解释
mysql_stmt_prepare 把 SQL 语句编译好,准备执行(比如预编译了 SELECT ... WHERE id = ?
mysql_stmt_bind_param 把你的输入参数(例如 id=1)绑定上
mysql_stmt_execute 执行语句
mysql_stmt_bind_result 把输出字段绑定到你提供的变量上
mysql_stmt_fetch 把结果读到你绑定的变量里
mysql_stmt_close 清理资源、关闭语句

MYSQL_STMT结构体的定义

cpp 复制代码
typedef struct MYSQL_STMT {
  struct MEM_ROOT *mem_root; /* 内存分配的根指针 */
  LIST list;                 /* 用于跟踪所有语句的列表 */
  MYSQL *mysql;              /* MySQL 连接句柄 */
  MYSQL_BIND *params;        /* 输入参数的绑定 */
  MYSQL_BIND *bind;          /* 输出参数的绑定 */
  MYSQL_FIELD *fields;       /* 结果集元数据 */
  MYSQL_DATA result;         /* 缓存的结果集 */
  MYSQL_ROWS *data_cursor;   /* 当前行在缓存数据中的位置 */
  /*
    mysql_stmt_fetch() 调用此函数以提取一行数据(不同的提取方式:缓冲、非缓冲、游标提取)。
  */
  int (*read_row_func)(struct MYSQL_STMT *stmt, unsigned char **row); /* 用于提取当前行数据的函数指针 */
  /* 语句执行后的 mysql->affected_rows 的副本 */
  uint64_t affected_rows;
  uint64_t insert_id;          /* mysql->insert_id 的副本 */
  unsigned long stmt_id;       /* 预处理语句的 ID */
  unsigned long flags;         /* 游标类型等标志 */
  unsigned long prefetch_rows; /* 每次 COM_FETCH 提取的行数 */
  /*
    从执行/提取后 mysql->server_status 复制,用于获取服务器端游标状态。
  */
  unsigned int server_status;
  unsigned int last_errno;            /* 错误码 */
  unsigned int param_count;           /* 输入参数的数量 */
  unsigned int field_count;           /* 结果集的列数 */
  enum enum_mysql_stmt_state state;   /* 语句状态 */
  char last_error[MYSQL_ERRMSG_SIZE]; /* 错误信息 */
  char sqlstate[SQLSTATE_LENGTH + 1]; /* SQL 错误状态码 */
  /* 输入参数类型是否已发送到服务器 */
  bool send_types_to_server;
  bool bind_param_done;           /* 输入缓冲区已提供 */
  unsigned char bind_result_done; /* 输出缓冲区已提供 */
  /* 如果 mysql_stmt_close() 必须取消此结果,则为 true */
  bool unbuffered_fetch_cancelled;
  /*
    如果需要在执行 mysql_stmt_store_result 时计算 field->max_length,
    则此字段设置为 true。
  */
  bool update_max_length;
  struct MYSQL_STMT_EXT *extension; /* 扩展字段,供 MySQL 内部使用 */
} MYSQL_STMT;

示例:

cpp 复制代码
//假设已经连接好了
// 初始化语句
MYSQL_STMT *stmt = mysql_stmt_init(conn);

// 准备 SQL(? 是参数占位符)
const char *sql = "SELECT name FROM users WHERE id = ?";
mysql_stmt_prepare(stmt, sql, strlen(sql));

// 输入参数绑定
MYSQL_BIND param;
int user_id = 1;
memset(&param, 0, sizeof(param));
param.buffer_type = MYSQL_TYPE_LONG;
param.buffer = &user_id;
mysql_stmt_bind_param(stmt, &param);

// 执行语句
mysql_stmt_execute(stmt);

// 结果绑定
MYSQL_BIND result;
char name[50];
unsigned long length;
memset(&result, 0, sizeof(result));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = name;
result.buffer_length = sizeof(name);
result.length = &length;
mysql_stmt_bind_result(stmt, &result);

// 释放资源
mysql_stmt_close(stmt);
mysql_close(conn);

此时的stmt为:

bash 复制代码
| stmt                 | 示例值 / 状态                    |           描述                           
| -------------------- | --------------------------------| ---------------------------- |
| `mysql`              | 指向连接句柄(非空)                     | 当前已连接到 MySQL 数据库。 
           
| `query`              | `"SELECT name FROM users WHERE id = ?"` | 原始 SQL 查询语句。                 

| `param_count`        | `1`                              | 只有一个输入参数,即 `id`。             

| `bind_param`         | 指向绑定了 `user_id = 1` 的 `MYSQL_BIND` 结构体  | 输入参数已绑定。                     

| `bind_result`        | 指向绑定了 `name` 输出缓冲区的 `MYSQL_BIND` 结构体 | 输出参数已绑定。                     
                 
| `state`              | `STMT_EXECUTED`                         | 表示语句已执行。                          |
| `insert_id`          | `0`(无插入)                      | 对 INSERT 语句有效。               |
| `error / last_errno` | `0`(无错误)                      | 若执行出错将有错误码和信息。               |
| `data_cursor`        | 指向当前行数据(非空)    | `mysql_stmt_fetch` 后用于遍历结果行。 
| `read_buffer`        | 内部分配                 | 存储查询返回的行数据内容。                |

<mysql.h>中常用的方法

1. 初始化与连接管理

mysql_init

用于初始化一个 MYSQL 结构体,为后续建立数据库连接做准备。它是使用 MySQL C API 的第一步。

cpp 复制代码
MYSQL* mysql_init(MYSQL* mysql);

参数:

mysql:一个 MYSQL 指针,可以是已分配的结构体,也可以传 NULL(推荐传 NULL,由库自动分配)。

返回值:

  • 成功:返回初始化后的 MYSQL * 指针。

  • 失败:返回 NULL(通常是因为内存不足)。

mysql_real_connect

建立一个真实的数据库连接。需要传入主机地址、用户名、密码、数据库名等信息。

cpp 复制代码
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_init() 初始化的连接句柄。

  • host:数据库服务器地址,如 "localhost"

  • user:用户名。

  • passwd:密码。

  • db:默认连接的数据库名(可为 NULL)。

  • port:端口号,默认 MySQL 为 3306。

  • unix_socket:Unix socket 文件路径,Linux 中可用,Windows 可传 NULL

  • client_flag:用于控制连接行为的标志位,如:

    • 0:默认连接方式。

    • CLIENT_MULTI_STATEMENTS:支持多语句执行。

    • CLIENT_SSL:启用 SSL 等。

返回值:

  • 成功:返回 MYSQL * 指针,表示连接成功。

  • 失败:返回 NULL,调用 mysql_error() 查看错误信息。

mysql_close

用于关闭一个已建立的数据库连接并释放资源,通常在程序结束时调用。

cpp 复制代码
void mysql_close(MYSQL *mysql);

参数:

  • mysql:由 mysql_init()mysql_real_connect() 获得的连接句柄。

返回值:

  • 无返回值,调用后连接资源被释放。

mysql_error

当连接或操作失败时,用于获取最近发生的错误信息(字符串形式)。

cpp 复制代码
const char *mysql_error(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

  • 返回最近一次 MySQL 操作失败的错误信息字符串。

示例

cpp 复制代码
#include <mysql/mysql.h>
#include <iostream>

int main() {
    // 初始化连接
    MYSQL *conn = mysql_init(NULL);
    if (conn == NULL) {
        std::cerr << "mysql_init() failed\n";
        return 1;
    }

    // 建立连接
    if (mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, NULL, 0) == NULL) {
        std::cerr << "mysql_real_connect() failed: " << mysql_error(conn) << "\n";
        mysql_close(conn); // 即使连接失败,也要释放句柄
        return 1;
    }

    std::cout << "Connected to database successfully!" << std::endl;

    // 关闭连接
    mysql_close(conn);
    return 0;
}

mysql_options

用于在连接建立之前为MYSQL*连接句柄设置参数,比如超时、字符集、重连等。

cpp 复制代码
int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg);

参数:

  • mysql:连接句柄(mysql_init 初始化后)。

  • option:选项类型,如:

    • MYSQL_OPT_CONNECT_TIMEOUT

    • MYSQL_SET_CHARSET_NAME

    • 这里不一一列出,具体参考mysql.h中的定义

  • arg:指向具体的设置值,如字符集名 "utf8mb4"

返回值:

  • 0:成功。

  • 非 0:失败。

mysql_set_character_set

连接成功后,设置当前连接使用的字符集。

cpp 复制代码
int mysql_set_character_set(MYSQL *mysql, const char *csname);

参数:

  • mysql:连接句柄。

  • csname:字符集名称,如 "utf8mb4"

返回值:

  • 0:成功。

  • 非 0:失败。

mysql_get_character_set_info

获取当前连接的字符集信息,包括名字、说明、最大字节数等。

cpp 复制代码
const MYSQL_CHARSET_INFO *mysql_get_character_set_info(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

返回一个指向 MYSQL_CHARSET_INFO 的结构体指针,结构体包含如下字段:

cpp 复制代码
struct charset_info_st {
    unsigned int  number;
    unsigned int  state;
    const char   *csname;     // 字符集名,如 "utf8mb4"
    const char   *name;       // 同上
    const char   *comment;    // 注释,如 "UTF-8 Unicode"
    const char   *dir;        // 编码文件路径(客户端库)
    unsigned int  mbminlen;   // 最小字节数
    unsigned int  mbmaxlen;   // 最大字节数
    ...
};

mysql_ping

测试当前连接是否有效;无效则尝试自动重连(如果启用自动重连)。

cpp 复制代码
int mysql_ping(MYSQL *mysql);

参数:

mysql:连接句柄。

返回值:

  • 返回 0 表示连接有效

  • 返回非零表示连接断开或检测失败

mysql_thread_id

获取当前连接在服务器端的线程 ID,可用于日志记录或跟踪问题。

cpp 复制代码
unsigned long mysql_thread_id(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

  • 返回该连接在服务器上的线程 ID

mysql_get_client_info

获取当前使用的 MySQL 客户端库的版本号,返回字符串形式。例如 "8.0.33"

cpp 复制代码
const char *mysql_get_client_info(void);

mysql_get_server_info

获取当前连接的 MySQL 服务器版本号(即远程服务器端的版本),返回字符串。

cpp 复制代码
const char *mysql_get_server_info(MYSQL *mysql);

示例:

cpp 复制代码
#include <mysql/mysql.h>
#include <iostream>

int main() {
    MYSQL *conn = mysql_init(NULL);
    if (!conn) {
        std::cerr << "mysql_init failed\n";
        return 1;
    }

    // 设置连接超时时间(3秒)
    int timeout = 3;
    mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);

    // 设置连接前字符集(也可连接后使用 mysql_set_character_set 设置)
    const char *charset = "utf8mb4";
    mysql_options(conn, MYSQL_SET_CHARSET_NAME, charset);

    // 建立连接
    if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, NULL, 0)) {
        std::cerr << "Connect failed: " << mysql_error(conn) << "\n";
        mysql_close(conn);
        return 1;
    }

    // 设置连接后字符集
    mysql_set_character_set(conn, "utf8mb4");

    // 打印连接信息
    std::cout << "Client version: " << mysql_get_client_info() << "\n";
    std::cout << "Server version: " << mysql_get_server_info(conn) << "\n";

    // 获取字符集信息
    const MYSQL_CHARSET_INFO *cs = mysql_get_character_set_info(conn);
    std::cout << "Character set: " << cs->csname << ", comment: " << cs->comment << "\n";

    // 检查连接是否有效
    if (mysql_ping(conn) == 0) {
        std::cout << "Ping successful. Connection is alive.\n";
    }

    // 打印线程 ID
    std::cout << "Connection thread ID: " << mysql_thread_id(conn) << "\n";

    mysql_close(conn);
    return 0;
}

mysql_shutdown

此函数用于关闭 MySQL 服务器。必须具有管理员权限来执行此操作。

cpp 复制代码
int mysql_shutdown(MYSQL *mysql, unsigned int shutdown_level, const char *shutdown_message);

参数:

  • mysql:已连接的 MYSQL 句柄。

  • shutdown_level:关闭级别:

    • SHUTDOWN_DEFAULT:常规关闭。

    • SHUTDOWN_WAIT:稍微延迟关闭(避免连接断开)。

    • SHUTDOWN_GRACEFUL:平滑关闭(防止断开已建立的连接)。

  • shutdown_message:可选的关闭消息(可为 NULL)。

返回值:

  • 成功:返回 0。

  • 失败:返回非 0,调用 mysql_error() 获取错误信息。

mysql_refresh

刷新 MySQL 服务器的缓存或状态。常用于刷新日志、权限或其他内部状态。

cpp 复制代码
int mysql_refresh(MYSQL *mysql, unsigned int refresh_options);

参数说明:

  • mysql:已连接的 MYSQL 句柄。

  • refresh_options:刷新选项,组合为 OR:

    • REFRESH_GRANT:刷新权限。

    • REFRESH_LOG:刷新二进制日志。

    • REFRESH_TABLES:刷新表缓存。

    • REFRESH_HOSTS:刷新主机缓存。

    • REFRESH_STATUS:刷新状态信息。

返回值:

  • 成功返回 0,失败返回非 0。

mysql_kill

终止 MySQL 服务器中的线程。此操作需要管理员权限。

cpp 复制代码
int mysql_kill(MYSQL *mysql, unsigned long thread_id);

参数说明:

  • mysql:已连接的 MYSQL 句柄。

  • thread_id:要终止的线程 ID。

返回值:

  • 成功:返回 0。

  • 失败:返回非 0,调用 mysql_error() 获取错误信息。

mysql_stat

获取 MySQL 服务器的状态信息。此函数返回一系列关于服务器运行状态的统计数据。

cpp 复制代码
char *mysql_stat(MYSQL *mysql);

参数:

  • mysql:已连接的 MYSQL 句柄。

返回值:

  • 返回服务器状态信息的字符串。

  • 失败时返回 NULL,调用 mysql_error() 获取错误信息。

示例:

cpp 复制代码
#include <mysql/mysql.h>
#include <cstdio>
#include <cstring>

int main() {
    MYSQL *conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, "localhost", "root", "password", "test_db", 0, NULL, 0)) {
        printf("Connection failed: %s\n", mysql_error(conn));
        return 1;
    }

    // 获取服务器状态
    char *status = mysql_stat(conn);
    if (status) {
        printf("Server Status: %s\n", status);
    } else {
        printf("Error retrieving server status: %s\n", mysql_error(conn));
    }

    // 刷新服务器(刷新权限、日志等)
    if (mysql_refresh(conn, REFRESH_GRANT | REFRESH_LOG)) {
        printf("Error refreshing server: %s\n", mysql_error(conn));
    } else {
        printf("Server refreshed successfully.\n");
    }

    // 假设要终止线程 ID 为 5
    if (mysql_kill(conn, 5)) {
        printf("Error killing thread: %s\n", mysql_error(conn));
    } else {
        printf("Thread killed successfully.\n");
    }

    // 关闭服务器
    if (mysql_shutdown(conn, SHUTDOWN_DEFAULT, "Shutting down server")) {
        printf("Error shutting down server: %s\n", mysql_error(conn));
    } else {
        printf("Server shut down successfully.\n");
    }

    mysql_close(conn);
    return 0;
}

2. 执行 SQL(直接执行)

mysql_query

用于向数据库发送一条以 C 字符串形式的 SQL 查询。适用于不包含二进制数据的简单 SQL 命令(如 SELECTINSERTUPDATE 等)。

cpp 复制代码
int mysql_query(MYSQL *mysql, const char *stmt_str);

参数:

  • mysql:指向已初始化并连接成功的 MYSQL 连接句柄。

  • stmt_str:SQL 查询语句,以 C 字符串形式提供。

返回值:

  • 成功:返回 0

  • 失败:返回非零,可通过 mysql_error() 获取错误信息。

mysql_real_query

mysql_query 类似,但可以处理二进制数据(包括内嵌的空字符 \0),适用于更复杂的 SQL 语句。

cpp 复制代码
int mysql_real_query(MYSQL *mysql, const char *stmt_str, unsigned long length);

参数:

  • mysql:数据库连接句柄。

  • stmt_str:指向 SQL 查询语句的指针。

  • length:语句的字节长度(不一定是 strlen(stmt_str),尤其当语句中含有 \0 时)。

返回值:

  • 成功:返回 0

  • 失败:返回非零,可用 mysql_error() 获取错误信息。

mysql_escape_string

对字符串进行转义,防止 SQL 注入攻击。适用于未连接数据库的简单转义需求

为什么需要"转义字符串"?

当拼接 SQL 语句时,如果直接将用户输入的数据写入 SQL 字符串中,就可能导致 SQL 注入攻击

示例:

cpp 复制代码
const char* username = "O'Reilly";
char query[256];
sprintf(query, "INSERT INTO users(name) VALUES('%s')", username);

拼接结果是:

cpp 复制代码
INSERT INTO users(name) VALUES('O'Reilly')

由于名字中含有 ',SQL 会认为 'O' 是一个字符串,而 Reilly' 就变成了非法语法,这会导致 SQL 报错,甚至黑客可以通过精心构造的字符串攻击数据库。

解决办法:用 转义函数 处理字符串mysql_escape_string

这个函数的作用就是将输入字符串中的特殊字符(比如 ', ", \, NULL 字节等)转义成安全的形式,以防止拼接 SQL 时出错或被攻击。

⚠️ 注意:这个函数不依赖数据库连接 ,只进行简单的 ASCII 级别转义,不考虑字符集编码问题,所以只能在最简单的场景中使用。

函数原型:

cpp 复制代码
unsigned long mysql_escape_string(char *to, const char *from, unsigned long length);

参数:

  • to:目标缓冲区(用于保存转义后的字符串)。

  • from:原始字符串。

  • length:原始字符串长度。

返回值:

  • 返回写入 to 的字节数(不包括终止符 \0)。

mysql_real_escape_string

mysql_escape_string 类似,但使用连接信息(如字符集)进行转义,它需要一个已经建立连接的 MYSQL * 参数。更推荐使用

cpp 复制代码
unsigned long mysql_real_escape_string(MYSQL *mysql,
                                       char *to,
                                       const char *from,
                                       unsigned long length);

参数:

  • mysql:数据库连接句柄(用于获取字符集信息)。

  • to:目标缓冲区。

  • from:原始字符串。

  • length:原始字符串长度。

返回值:

  • 返回写入 to 的字节数。

示例:

cpp 复制代码
#include <mysql/mysql.h>
#include <stdio.h>
#include <string.h>

int main() {
    MYSQL *conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 0, NULL, 0)) {
        fprintf(stderr, "Connection failed: %s\n", mysql_error(conn));
        return 1;
    }

    // 用户输入需要转义
    const char *user_input = "O'Reilly";
    char escaped[256];
    mysql_real_escape_string(conn, escaped, user_input, strlen(user_input));

    // 拼接 SQL 语句
    char query[512];
    snprintf(query, sizeof(query), "INSERT INTO users(name) VALUES('%s')", escaped);

    // 使用 mysql_real_query 执行语句
    if (mysql_real_query(conn, query, strlen(query))) {
        fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
    } else {
        printf("Insert successful.\n");
    }

    mysql_close(conn);
    return 0;
}

3. 结果集处理(MYSQL_RES / MYSQL_ROW)

第一组:结果集的获取与释放

mysql_store_result

从服务器读取完整结果集并缓存在客户端内存中。

注意:必须先执行过一条会返回结果的 SQL 语句,比如 SELECT,然后才能调用 mysql_store_result() 来获取"那条语句"的结果。

cpp 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);

参数:

  • mysql:已连接的 MySQL 句柄。

返回值

  • 成功:返回 MYSQL_RES * 指针。

  • 无结果或失败:返回 NULL。用 mysql_errno() 区分错误或无结果。

使用流程

cpp 复制代码
mysql_query(conn, "SELECT id, name FROM users");  // 1. 先执行 SQL 查询
MYSQL_RES* res = mysql_store_result(conn);       // 2. 再获取结果集

mysql_use_result

初始化结果集,但不缓存整个结果集,适用于大结果集,节省内存,按行读取。

cpp 复制代码
MYSQL_RES *mysql_use_result(MYSQL *mysql);

参数:

  • mysql:已经连接成功的 MYSQL 对象指针。

返回值:

  • 成功:返回指向 MYSQL_RES 的指针。

  • 失败或无结果:返回 NULL,可以用 mysql_errno() 判断原因。

注意事项 :使用完后必须使用 mysql_free_result() 释放,否则可能导致连接阻塞。

mysql_use_result()mysql_store_result() 的最大区别是------它不会一次性从服务器获取并缓存整个结果集,而是每次调用 mysql_fetch_row() 时,从服务器取一行。

也就是说:

  • t不缓存全部行 → 节省客户端内存。

  • 不能随机访问 → 不支持 mysql_data_seek()mysql_row_seek()

  • 调用期间不能执行新的 SQL 查询 → 因为结果还"留"在服务器端。

cpp 复制代码
#include <mysql/mysql.h>
#include <iostream>

int main() {
    .........
    // 执行查询语句
    if (mysql_query(conn, "SELECT id, name FROM users")) {
        std::cerr << "mysql_query failed: " << mysql_error(conn) << "\n";
        mysql_close(conn);
        return 1;
    }

    // 使用 mysql_use_result 获取结果(逐行获取,节省内存)
    MYSQL_RES *res = mysql_use_result(conn);
    if (!res) {
        std::cerr << "mysql_use_result failed: " << mysql_error(conn) << "\n";
        mysql_close(conn);
        return 1;
    }

    // 获取列数
    unsigned int num_fields = mysql_num_fields(res);

    // 遍历结果集
    MYSQL_ROW row;
    while ((row = mysql_fetch_row(res))) {
        for (unsigned int i = 0; i < num_fields; ++i) {
            std::cout << (row[i] ? row[i] : "NULL") << "\t";
        }
        std::cout << "\n";
    }

    // 清理
    mysql_free_result(res);
    return 0;
}

mysql_free_result

释放由 mysql_store_result()mysql_use_result() 获取的结果集,避免内存泄漏。

cpp 复制代码
void mysql_free_result(MYSQL_RES *result);

参数

  • result:指向 MYSQL_RES 的结果集指针。

第二组:逐行读取数据

mysql_fetch_row

从结果集中提取下一行数据。

cpp 复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

参数

  • result:由 mysql_store_result()mysql_use_result() 返回的结果集。

返回值

  • 成功:返回类型为 MYSQL_ROW(实质为 char **)的指针,表示一行数据。

  • 结束或无数据:返回 NULL

mysql_fetch_lengths

获取当前行中每一列的数据长度(以字节为单位)。

cpp 复制代码
unsigned long *mysql_fetch_lengths(MYSQL_RES *result);

参数

  • result:当前结果集,且必须在调用了 mysql_fetch_row() 之后调用本函数。

返回值

  • 成功:返回一个 unsigned long 类型的数组,每个元素表示当前行对应列的长度。

  • 失败:返回 NULL

示例:

cpp 复制代码
const char* query = "SELECT id, title, artist FROM songs";
if (mysql_query(conn, query)) {
   std::cerr << "Query failed: " << mysql_error(conn) << "\n";
   mysql_close(conn);
   return 1;
}

MYSQL_RES *res = mysql_store_result(conn);
if (!res) {
   std::cerr << "mysql_store_result failed: " << mysql_error(conn) << "\n";
   mysql_close(conn);
   return 1;
}

MYSQL_ROW row;

while ((row = mysql_fetch_row(res))) {
    unsigned long *lengths = mysql_fetch_lengths(res);
    ......
}

第三组:字段和元数据

mysql_num_rows

返回结果集的总行数。只适用于 mysql_store_result

参数

  • res:结果集指针。

返回值

  • 返回一个 my_ulonglong 类型的整数,表示结果集中的行数。

mysql_num_fields

返回结果集中字段(列)的数量。

cpp 复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

  • 字段总数(列数)。

mysql_fetch_field

逐个提取字段的结构体信息,如字段名、类型、长度等。

cpp 复制代码
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result);

参数

  • result:结果集指针。

返回值

  • 成功:返回 MYSQL_FIELD*

  • 所有字段遍历完毕:返回 NULL

mysql_fetch_fields

一次性返回所有字段信息的数组。

cpp 复制代码
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

  • 成功:返回字段数组首地址(MYSQL_FIELD*)。

  • 失败:返回 NULL

mysql_fetch_field_direct

根据索引直接获取字段信息。

cpp 复制代码
MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr);

参数

  • res:结果集指针。

  • fieldnr:字段索引(从 0 开始)。

返回值

  • 返回对应索引位置的字段信息指针。

mysql_field_seek

设置字段遍历的偏移量,下一次 mysql_fetch_field() 会从该偏移开始。

cpp 复制代码
MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *res, MYSQL_FIELD_OFFSET offset);

参数

  • res:结果集指针。

  • offset:字段偏移值(可由 mysql_field_tell() 获取)。

返回值

  • 返回设置前的位置(类型为 MYSQL_FIELD_OFFSET)。

mysql_field_tell

获取当前字段指针的位置,可用于后续调用 mysql_field_seek() 返回到当前位置。

cpp 复制代码
MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

  • 返回当前字段位置偏移量。

示例:

bash 复制代码
| id | title     | artist      |
| -- | --------- | ----------- |
| 1  | Yesterday | The Beatles |
| 2  | Hello     | Adele       |
| 3  | Imagine   | John Lennon |
cpp 复制代码
#include <mysql/mysql.h>
#include <iostream>
#include <cstdlib>

int main() {
    MYSQL *conn = mysql_init(nullptr);
    if (!conn) {
        std::cerr << "mysql_init failed\n";
        return 1;
    }

    // 建立连接
    if (!mysql_real_connect(conn, "localhost", "root", "123456", "test_db", 0, nullptr, 0)) {
        std::cerr << "Connection failed: " << mysql_error(conn) << "\n";
        return 1;
    }

    // 查询语句
    const char* query = "SELECT id, title, artist FROM songs";
    if (mysql_query(conn, query)) {
        std::cerr << "Query failed: " << mysql_error(conn) << "\n";
        mysql_close(conn);
        return 1;
    }

    // 结果集
    MYSQL_RES *res = mysql_store_result(conn);
    if (!res) {
        std::cerr << "mysql_store_result failed: " << mysql_error(conn) << "\n";
        mysql_close(conn);
        return 1;
    }

    // 1. 获取总行数
    my_ulonglong row_count = mysql_num_rows(res);
    std::cout << "总行数: " << row_count << "\n";  // 示例输出: 总行数: 3

    // 2. 获取字段总数
    unsigned int field_count = mysql_num_fields(res);
    std::cout << "字段数: " << field_count << "\n";  // 示例输出: 字段数: 3

    // 3. 使用 mysql_fetch_fields 获取所有字段信息
    MYSQL_FIELD *fields = mysql_fetch_fields(res);
    

    // 4. 使用 mysql_field_seek 和 mysql_fetch_field 重置字段位置并逐个读取
    MYSQL_FIELD_OFFSET offset = mysql_field_seek(res, 0);  // 重置位置
    MYSQL_FIELD *field;
    while ((field = mysql_fetch_field(res))) {
        ......
    }

    // 5. 使用 mysql_field_tell 获取当前位置(已走到结尾)
    MYSQL_FIELD_OFFSET current_pos = mysql_field_tell(res);
    std::cout << "\n当前字段偏移位置 = " << current_pos << "\n";  // 示例输出: 当前字段偏移位置 = 3

    // 6. 使用 mysql_fetch_field_direct 直接访问字段信息
    std::cout << "\n使用 fetch_field_direct:\n";
    for (unsigned int i = 0; i < field_count; ++i) {
        field = mysql_fetch_field_direct(res, i);
        std::cout << "字段[" << i << "]: 名称 = " << field->name << ", 类型 = " << field->type << "\n";
    }

    mysql_free_result(res);
    mysql_close(conn);
    return 0;
}

4.预处理语句(MYSQL_STMT)

预处理语句:

预处理语句是数据库提供的一种 高效、安全的执行 SQL 语句 的机制,它将 SQL 语句的结构和数据分开处理。

预处理语句先告诉数据库:"我要做一件什么事(SQL结构)",然后再告诉它:"我要用哪些数据来做这件事"。也就是说,预处理语句把一个完整的 SQL 操作分成 两步走

第一步:定义你要做的事情(SQL语句结构)
第二步:告诉它你要用哪些数据(填入参数)

示例:

假设有一条插入语句:

sql 复制代码
INSERT INTO users (name, age) VALUES ('Alice', 25);

在 C /c++代码里用字符串拼接是这样写的:

cpp 复制代码
char sql[100];
sprintf(sql, "INSERT INTO users (name, age) VALUES ('%s', %d);", name, age);
mysql_query(conn, sql);

问题:

  • 这容易出错(比如字符串中有 ' 或特殊字符)

  • 还有严重的 SQL 注入风险!

改用预处理语句后把 SQL 写成这样:

sql 复制代码
INSERT INTO users (name, age) VALUES (?, ?);
  • ? 是占位符,代表后面你再填入的参数。

  • SQL 结构不变,数据单独绑定,不会被解释为语句的一部分。

  • 执行流程由你控制,像"先建模,再灌数据"。

预处理语句主要优势:

①防止 SQL 注入攻击(安全性高)

使用预处理语句时,SQL 语句中使用 ? 占位符 来代表参数,不再通过字符串拼接直接构造 SQL。数据(参数)与语句分离,数据库会将参数 按值处理,不会被解释为 SQL 指令。

不安全(传统拼接):

sql 复制代码
char sql[200];
sprintf(sql, "SELECT * FROM users WHERE username = '%s'", user_input);  // 易受注入攻击

SQL 注入是指:攻击者把恶意的 SQL 代码,伪装成用户输入,拼接进原本的 SQL 语句中,从而控制数据库行为,甚至获取敏感数据、删除表等。
示例:
用户输入的是:user_input = "Alice";
拼接后的 SQL 是:SELECT * FROM users WHERE username = 'Alice';
这个 SQL 没有问题,会查找 username = 'Alice' 的用户。

但如果攻击者输入的是:user_input = "' OR '1'='1";
拼接后的 SQL 就变成了:SELECT * FROM users WHERE username = '' OR '1'='1';
这意味着:只要条件 1=1 成立(它永远成立),就会返回数据库中所有用户信息!

所以 SQL 注入本质是:
利用你把用户输入当作 SQL 的一部分来拼接字符串,伪造出攻击性的 SQL。

安全(预处理):

sql 复制代码
SELECT * FROM users WHERE username = ?

然后通过 mysql_stmt_bind_param 将参数单独传入,数据库会把参数值当作纯文本。

②性能更高(尤其适合重复执行)

预处理语句将 SQL 的 解析、语法检查和优化 阶段只做一次。你可以多次 绑定不同参数 进行执行,数据库会重用之前编译好的语句结构。

适用场景:

  • 同一条 SQL 要反复执行,例如插入大量数据(批量 INSERT)。

  • 每次数据不同但 SQL 结构不变。

第一组:初始化与准备

mysql_stmt_init

初始化一个预处理语句句柄。

sql 复制代码
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);

参数

  • mysql:已连接的 MYSQL 对象指针。

返回值

  • 成功:返回新分配的 MYSQL_STMT 指针。

  • 失败:返回 NULL

mysql_stmt_prepare

为预处理语句对象编译 SQL 语句。

sql 复制代码
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);

参数

  • stmt:预处理语句对象。

  • query:SQL 查询语句字符串(使用 ? 占位符)。

  • length:SQL 字符串的长度(以字节为单位)。

返回值

  • 成功:返回 0。

  • 失败:返回非零,可使用 mysql_stmt_errno() 获取错误码。

示例:

cpp 复制代码
MYSQL_STMT* stmt = mysql_stmt_init(conn);
const char* sql = "INSERT INFO songs (id, title, artist) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, sql, strlen(sql))) {
    std::cout << "some error" << mysql_stmt_error(stmt));
}

第二组:参数绑定与执行

mysql_stmt_bind_param

将数据绑定到预处理语句的参数中。

cpp 复制代码
int mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind);

参数

  • stmt:预处理语句对象。

  • bindMYSQL_BIND 数组,每个元素绑定一个参数。

返回值

  • 成功:返回 0。

  • 失败:返回非零。

mysql_stmt_execute

执行已准备好的语句。

cpp 复制代码
int mysql_stmt_execute(MYSQL_STMT *stmt);

参数

  • stmt:已准备并绑定参数的语句句柄。

返回值

  • 成功:返回 0。

  • 失败:返回非零。

示例:

cpp 复制代码
int id = 4;
char title[100] = "acdscadscas";
char artist[100] = "acdsdcd";

MYSQL_BIND bind[3];
memset(bind, 0, sizeof(bind));

bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = &id;

bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = title;
bind[1].buffer_length = sizeof(title);

bind[2].buffer_type = MYSQL_TYPE_STRING;
bind[2].buffer = artist;
bind[2].buffer_length = sizeof(artist);

if (mysql_stmt_bind_param(stmt, bind) != 0 ||
    mysql_stmt_execute(stmt) != 0) {
    std::cout << "some error" << mysql_stmt_error(stmt));
}

第三组:结果绑定与提取

mysql_stmt_bind_result

将变量绑定到语句的输出结果列。

cpp 复制代码
int mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);

参数

  • stmt:执行了 SELECT 的语句句柄。

  • bind:用于接收结果的变量数组。

返回值

  • 成功:返回 0。

  • 失败:返回非零。

mysql_stmt_fetch

逐行获取结果。

cpp 复制代码
int mysql_stmt_fetch(MYSQL_STMT *stmt);

参数

  • stmt:结果已绑定的语句句柄。

返回值

  • 成功:返回 0。

  • 没有更多行:返回 MYSQL_NO_DATA

  • 错误:返回 1

示例:

cpp 复制代码
MYSQL_STMT *select_stmt = mysql_stmt_init(conn);
const char *sql = "SELECT id, title FROM songs WHERE artist = ?";
mysql_stmt_prepare(select_stmt, sql, strlen(sql));

char artist_filter[] = "The Beatles";
MYSQL_BIND param[1];
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_STRING;
param[0].buffer = artist_filter;
param[0].buffer_length = strlen(artist_filter);

mysql_stmt_bind_param(select_stmt, param);
mysql_stmt_execute(select_stmt);

int id;
char title[100];
MYSQL_BIND result[2];
memset(result, 0, sizeof(result));
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = &id;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = title;
result[1].buffer_length = sizeof(title);

mysql_stmt_bind_result(select_stmt, result);

while (mysql_stmt_fetch(select_stmt) == 0) {
    printf("id: %d, title: %s\n", id, title);
}

第四组:结果缓存与元数据相关函数

mysql_stmt_store_result

将结果集从服务器读取并缓存到客户端,适用于随机访问或多次访问结果的场景。

cpp 复制代码
int mysql_stmt_store_result(MYSQL_STMT *stmt);

参数

  • stmt:执行了 SELECT 的语句句柄。

返回值

  • 成功:返回 0

  • 失败:返回非零,使用 mysql_stmt_errno() 获取错误码。

注意:

  • 如果你需要使用 mysql_stmt_num_rows() 或者 mysql_stmt_data_seek(),必须先调用本函数。

  • 类似于 mysql_store_result(),但用于预处理语句。

mysql_stmt_result_metadata

获取结果集的字段元信息(例如列名、类型、长度等)。

cpp 复制代码
MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt);

参数

  • stmt:准备并执行了 SELECT 的语句句柄。

返回值

  • 成功:返回 MYSQL_RES *,可通过 mysql_fetch_field()mysql_num_fields() 等分析字段结构。

  • 失败或无字段结果:返回 NULL

用途

  • 当你想动态绑定结果时(不知道返回字段数),可先调用本函数来获知字段信息。

mysql_stmt_param_metadata

获取语句的参数信息(占位符参数的数量、类型等)。

cpp 复制代码
MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt);

参数

  • stmt:已经调用 mysql_stmt_prepare() 的语句句柄。

返回值

  • 成功:返回 MYSQL_RES *,可用于查看参数数量和类型。

  • 无参数或失败:返回 NULL

示例:

cpp 复制代码
mysql_stmt_prepare(stmt, sql, strlen(sql));

// 绑定输入参数
char artist[] = "The Beatles";
MYSQL_BIND param[1];
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_STRING;
param[0].buffer = artist;
param[0].buffer_length = strlen(artist);
mysql_stmt_bind_param(stmt, param);
mysql_stmt_execute(stmt);

// 获取元数据(字段名、类型等)
MYSQL_RES *meta_result = mysql_stmt_result_metadata(stmt);

// 获取结果集中字段数量
unsigned int field_count = mysql_num_fields(meta_result);
// 假设 SQL 为:SELECT id, title FROM songs WHERE artist = ?
// 那么字段数量为 2(即 id 和 title)
/*
    field_count = 2
*/

// 获取字段数组,包含每个字段的元信息(字段名、类型等)
MYSQL_FIELD *fields = mysql_fetch_fields(meta_result);
/*
    fields[0].name = "id"
    fields[0].type = MYSQL_TYPE_LONG (对应数字类型 INT)

    fields[1].name = "title"
    fields[1].type = MYSQL_TYPE_STRING 或 MYSQL_TYPE_VAR_STRING(VARCHAR 类型)
*/

// 打印字段元信息
for (unsigned int i = 0; i < field_count; i++) {
    printf("Column %d: name = %s, type = %d\n", i, fields[i].name, fields[i].type);
}

/*
    上面循环的实际输出将为:
    Column 0: name = id, type = 3
    Column 1: name = title, type = 253

    注:
    type = 3   表示 MYSQL_TYPE_LONG(整数)
    type = 253 表示 MYSQL_TYPE_VAR_STRING(VARCHAR)
*/

// 获取结果并缓存(必须在 fetch 前调用,允许随机访问结果)
mysql_stmt_store_result(stmt);

mysql_stmt_num_rows

获取结果集中总行数(在 mysql_stmt_store_result() 之后使用)

cpp 复制代码
my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt);

参数

  • stmt:已执行并缓存结果的语句句柄。

返回值

  • 成功:返回结果集中的总行数。

  • 失败:返回 0。

示例:

cpp 复制代码
mysql_stmt_store_result(stmt);
my_ulonglong row_count = mysql_stmt_num_rows(stmt);

mysql_stmt_data_seek

移动结果集的内部行指针,用于"跳转"读取特定行(仅适用于 store_result() 缓存模式)

cpp 复制代码
void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset);

参数

  • stmt:语句句柄

  • offset:目标行索引(从 0 开始)

cpp 复制代码
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *sql = "SELECT id, title FROM songs";
mysql_stmt_prepare(stmt, sql, strlen(sql));
mysql_stmt_execute(stmt);

// 绑定结果
int id;
char title[100];
MYSQL_BIND result[2];
memset(result, 0, sizeof(result));
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = &id;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = title;
result[1].buffer_length = sizeof(title);
mysql_stmt_bind_result(stmt, result);

// 缓存结果(这是使用 data_seek 的前提)
mysql_stmt_store_result(stmt);

// 第一次 fetch 读取结果集中的第一行
mysql_stmt_fetch(stmt);
printf("[第一次] id: %d, title: %s\n", id, title);  
// 输出:[第一次] id: 1, title: Yesterday

mysql_stmt_fetch(stmt); 读取结果集中的第二行
printf("[第二次] id: %d, title: %s\n", id, title);  
// 输出:[第二次] id: 2, title: Hello

// 使用 data_seek 跳回第 0 行(第一行)
mysql_stmt_data_seek(stmt, 0);

// 再 fetch 一次,应该是第一行
mysql_stmt_fetch(stmt);
printf("[跳回后] id: %d, title: %s\n", id, title);  
// 输出:[跳回后] id: 1, title: Yesterday

mysql_stmt_free_result

释放语句对象中存储的结果集内存,释放后不能再调用 fetch

cpp 复制代码
int mysql_stmt_free_result(MYSQL_STMT *stmt);

参数

  • stmt:语句句柄

返回值

  • 成功:返回 0

  • 失败:返回非零

cpp 复制代码
mysql_stmt_free_result(stmt);

调用时机:在完成数据读取后及时释放内存。

第五组:预处理语句的辅助与控制函数

mysql_stmt_attr_set

设置语句句柄的属性,例如设置最大缓冲行数(可控制 mysql_stmt_store_result 的行为)。

cpp 复制代码
int mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr);

参数:

  • stmt:预处理语句句柄。

  • attr_type:属性类型,常用值包括:

    • STMT_ATTR_UPDATE_MAX_LENGTH:是否更新字段的最大长度信息。

    • STMT_ATTR_CURSOR_TYPE:设置游标类型(如 CURSOR_TYPE_READ_ONLY)。

    • STMT_ATTR_PREFETCH_ROWS:设置预取行数(优化 fetch 行为)。

  • attr:指向属性值的指针。

返回值

  • 成功:0

  • 失败:非零

mysql_stmt_attr_get

获取语句当前某个属性的值。

cpp 复制代码
int mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *attr);

参数

attr_set 类似,只是 attr 是用来接收属性值的指针。

返回值

成功返回 0,失败返回非零。

示例:

设置并获取 STMT_ATTR_UPDATE_MAX_LENGTH 属性

这个属性的含义是:

是否在**mysql_stmt_store_result() 后自动更新字段的最大长度(比如字符串字段的最大长度)** 。

设置为 1 表示"更新字段最大长度信息",设置为 0 表示"不更新"。

cpp 复制代码
// 设置属性:开启字段最大长度自动更新
my_bool set_val = 1;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &set_val) != 0) {
    fprintf(stderr, "Failed to set stmt attribute\n");
}

// 获取属性:验证设置是否生效
my_bool get_val = 0;
if (mysql_stmt_attr_get(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &get_val) != 0) {
    fprintf(stderr, "Failed to get stmt attribute\n");
} else {
    printf("STMT_ATTR_UPDATE_MAX_LENGTH = %d\n", get_val);  
    // 输出应为 1,表示设置成功
}

mysql_stmt_errno

获取最近一条预处理语句操作的错误码。

cpp 复制代码
unsigned int mysql_stmt_errno(MYSQL_STMT *stmt);

mysql_stmt_error

获取最近一条语句操作的错误信息(字符串形式)。

cpp 复制代码
const char *mysql_stmt_error(MYSQL_STMT *stmt);

mysql_stmt_reset

重置语句(清除绑定的参数与结果、状态等,但不释放语句)。

cpp 复制代码
int mysql_stmt_reset(MYSQL_STMT *stmt);

mysql_stmt_close

关闭语句并释放预处理语句句柄的所有资源。

cpp 复制代码
int mysql_stmt_close(MYSQL_STMT *stmt);

示例:

cpp 复制代码
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *sql = "SELECT title FROM songs";
if (mysql_stmt_prepare(stmt, sql, strlen(sql))) {
    fprintf(stderr, "Prepare failed: %s\n", mysql_stmt_error(stmt));
    return;
}
........
// 清理
mysql_free_result(meta);
mysql_stmt_close(stmt);

5. 事务控制

事务是什么?

事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

一句话:事务就是一组必须"要么全做完,要么一个也不做"的数据库操作。

比如:银行转账操作 ------ 张三转账给李四 1000 元,不能出现张三的钱扣了但李四没收到的情况。

示例:

sql 复制代码
-- 1. 查询张三账户余额
select * from account where name = '张三';
-- 2. 将张三账户余额-1000
update account set money = money - 1000 where name = '张三';
-- 此语句出错后张三钱减少但是李四钱没有增加
模拟sql语句错误
-- 3. 将李四账户余额+1000
update account set money = money + 1000 where name = '李四';
 这时如果没有事务:

此时张三的钱就被扣了,李四的钱却没有增加,钱"凭空消失",数据错误了!

所以必须让这步操作打包成一组事务,要么一起成功、要么一起失败回滚。

mysql_autocommit

这个函数是事务控制的开关,控制是否开启"自动提交模式"。

  • 默认情况下,MySQL 每执行一条 INSERTUPDATEDELETE 就会立即 COMMIT,这叫"自动提交"。

  • 如果你希望在多条 SQL 执行后统一提交或撤销(比如钱转了一半不能让数据库写进去),就需要关闭自动提交,手动控制事务。

cpp 复制代码
int mysql_autocommit(MYSQL *mysql, my_bool mode);

参数:

  • mysql:连接句柄。

  • mode

    • 1(默认):开启自动提交模式(每条语句执行后自动提交)。

    • 0:关闭自动提交,意味着你要手动用 mysql_commit()mysql_rollback() 控制事务提交。

返回值:
  • 成功返回 0,失败返回非 0。
cpp 复制代码
mysql_autocommit(conn, 0); // 相当于 START TRANSACTION;

mysql_commit

这个函数提交事务,将你执行的所有数据更改操作真正"写入数据库"。

  • 如果你执行了多条修改语句(例如插入了几条订单信息),但还没调用 mysql_commit(),那么这些修改还没有真正写入磁盘(处于事务缓存中)。

  • 只有调用 mysql_commit(),事务才算真正结束,数据才会变为持久化。

cpp 复制代码
int mysql_commit(MYSQL *mysql);

返回值:

  • 成功返回 0,失败返回非 0。
注意:

在使用事务时,如果你还没有调用 mysql_commit() 提交事务 ,此时数据库连接突然断掉(比如网络断了、程序崩溃了、数据库宕机了),那么:当前正在执行的事务会被自动取消,数据库会帮你回滚!

示例:

假设执行了一系列数据库操作,比如:

cpp 复制代码
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;

这时候你还没调用 COMMIT; ------ 也就是说事务还在进行中。

但突然:

  • 程序崩溃了,

  • 数据库断网了,

  • 或者数据库服务挂了,

那么:这两个 UPDATE 操作 都不会被保存MySQL 会自动回滚这些未提交的修改

mysql_rollback

这个函数是 commit() 的"反操作":撤销当前事务中的所有修改。如果你在执行多条语句后发现某一步出错,就可以调用它,防止错误写入数据库。

cpp 复制代码
int mysql_rollback(MYSQL *mysql);

返回值:

  • 成功返回 0,失败返回非 0。

示例:

cpp 复制代码
// 订单支付逻辑
mysql_autocommit(conn, 0);

if (mysql_query(conn, "INSERT INTO orders ...") ||
    mysql_query(conn, "UPDATE users SET balance = balance - 100 ...")) {
    // 有一步失败,撤销所有操作
    mysql_rollback(conn);
} else {
    mysql_commit(conn); // 所有操作成功,确认写入
}

mysql_more_results

这个函数用于判断你是否还有下一个结果集 可读取,通常和 mysql_next_result() 搭配。

通常用于多语句查询(multi-statement),比如:

cpp 复制代码
SELECT * FROM table1; SELECT * FROM table2;

此时执行后会有两个结果集,而不是一个。

cpp 复制代码
bool mysql_more_results(MYSQL *mysql);
返回值:
  • true:还有下一个结果集。

  • false:所有结果集都已读取完。

mysql_next_result

这个函数让你进入下一个结果集 。它配合 mysql_more_results() 一起用。

比如执行的是:

cpp 复制代码
SELECT * FROM users; SELECT * FROM songs;

执行一次 mysql_store_result() 只能取到 users,想要取 songs 就需要用 mysql_next_result()

cpp 复制代码
int mysql_next_result(MYSQL *mysql);

返回值:

  • 成功:返回 0,说明成功切换到下一个结果集。

  • 没有更多结果:返回 -1

  • 错误:返回大于 0 的值。

cpp 复制代码
mysql_autocommit(conn, 0);  // 关闭自动提交,开启事务

// 执行多条 SQL(多语句查询)
const char *multi_sql = "INSERT INTO log(msg) VALUES('start'); SELECT * FROM songs;";
if (mysql_query(conn, multi_sql)) {
    fprintf(stderr, "执行失败:%s\n", mysql_error(conn));
    mysql_rollback(conn);
} else {
    // 提交事务
    mysql_commit(conn);

    // 获取第一结果(INSERT 无结果)
    MYSQL_RES *res = mysql_store_result(conn);
    if (res) mysql_free_result(res); // 忽略 INSERT 的结果集

    // 有更多结果?
    while (mysql_more_results(conn)) {
        if (mysql_next_result(conn) == 0) {
            res = mysql_store_result(conn);
            if (res) {
                MYSQL_ROW row;
                while ((row = mysql_fetch_row(res))) {
                    printf("歌曲名:%s\n", row[1]);
                }
                mysql_free_result(res);
            }
        }
    }
}

mysql_autocommit(conn, 1);  // 恢复自动提交模式
相关推荐
小毛驴8508 分钟前
Linux 后台启动java jar 程序 nohup java -jar
java·linux·jar
好好学习啊天天向上1 小时前
世上最全:ubuntu 上及天河超算上源码编译llvm遇到的坑,cmake,ninja完整过程
linux·运维·ubuntu·自动性能优化
好奇的菜鸟2 小时前
如何在IntelliJ IDEA中设置数据库连接全局共享
java·数据库·intellij-idea
tan180°2 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
满昕欢喜2 小时前
SQL Server从入门到项目实践(超值版)读书笔记 20
数据库·sql·sqlserver
典学长编程3 小时前
Linux操作系统从入门到精通!第二天(命令行)
linux·运维·chrome
wuk9983 小时前
基于MATLAB编制的锂离子电池伪二维模型
linux·windows·github
DuelCode3 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
幽络源小助理3 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
Hello.Reader4 小时前
Redis 延迟排查与优化全攻略
数据库·redis·缓存