当前位置: 首页 > news >正文

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

libmysqlclient-dev介绍

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_initmysql_query
/usr/include/mysql/mysql_version.h定义 MySQL 版本号和宏,例如 MYSQL_VERSION_ID
/usr/include/mysql/mysql_com.h定义客户端通信协议常量与结构,如 MYSQLMYSQL_FIELD
/usr/include/mysql/my_list.h提供链表结构定义,MySQL 内部用
动态库/usr/lib/x86_64-linux-gnu/libmysqlclient.so动态链接库,编译时链接使用,运行时加载
静态库/usr/lib/x86_64-linux-gnu/libmysqlclient.a静态链接库,可将 MySQL 客户端代码打包进可执行文件(较少用)
头文件搜索路径/usr/include/mysql/所有 MySQL C API 的头文件都放在这个目录下
符号链接/usr/lib/x86_64-linux-gnu/libmysqlclient.so -> libmysqlclient.so.X指向具体版本库文件的符号链接,供 -lmysqlclient 使用

libmysqlclient-dev的安装

sudo apt install libmysqlclient-dev

安装成功后的简单示例:

test_mysql.c

#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;
}

编译:

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结构体的定义

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;           // 插入操作返回的自增IDuint64_t extra_info;          // 未使用的扩展信息unsigned long thread_id;      // 服务器端的线程IDunsigned 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;

 示例:

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

| 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结构体的定义

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结构体的定义

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,它包含如下字段:

| 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 结构体存储的内容:

| `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结构体的定义

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 的数据库表,结构如下:

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

 假设我们执行的 SQL 查询:

SELECT id, name, age, created_at FROM users;

查询结果如下:

| 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 结构体存储的内容:

| `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结构体的定义

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;                  // 若为无符号整数类型,则此字段为 truebool long_data_used;               // 如果调用过 mysql_send_long_data,则此字段为 truebool is_null_value;                // 当 is_null 为 NULL 时使用此字段表示是否为 NULLvoid *extension;                   // 扩展字段,供 MySQL 内部使用
} MYSQL_BIND;

 输入参数绑定示例:

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

| id | name  |
| -- | ----- |
| 1  | Alice |
int main() {MYSQL *conn;MYSQL_STMT *stmt;MYSQL_BIND bind[2];// 假设已经连接到数据库// 准备 SQL 查询语句const char *query = "INSERT INTO users (id, name) VALUES (?, ?)";// 初始化 MYSQL_STMTstmt = 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; // 类型是 LONGbind[0].buffer = (char *)&id;          // 绑定值bind[0].is_null = 0;                   // 表示参数非 NULL// 绑定 name 参数bind[1].buffer_type = MYSQL_TYPE_STRING; // 类型是 STRINGbind[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 结构体绑定输出参数,并从查询结果中获取数据。

| id | name  |
| -- | ----- |
| 1  | Alice |
int main() {MYSQL *conn;MYSQL_STMT *stmt;MYSQL_BIND bind[2]; // 绑定输出参数// 假设已经连接到数据库// 准备 SQL 查询语句const char *query = "SELECT id, name FROM users WHERE id = 1";// 初始化 MYSQL_STMTstmt = mysql_stmt_init(conn);// 设置输出变量unsigned long output_id;char output_name[50];// 清空绑定数组memset(bind, 0, sizeof(bind));// 绑定输出参数 idbind[0].buffer_type = MYSQL_TYPE_LONG;  // 类型是 LONGbind[0].buffer = (char *)&output_id;    // 输出的 id 参数bind[0].is_null = 0;                    // 非 NULLbind[0].length = 0;// 绑定输出参数 namebind[1].buffer_type = MYSQL_TYPE_STRING; // 类型是 STRINGbind[1].buffer = (char *)output_name;    // 输出的 name 字符串bind[1].buffer_length = sizeof(output_name); // 输出缓冲区长度bind[1].is_null = 0;                    // 非 NULLbind[1].length = 0;// 将绑定的输出参数应用到语句中mysql_stmt_bind_result(stmt, bind); // 绑定输出参数// 执行查询mysql_stmt_execute(stmt);return 0;
}

此时bind[2]的结果是:

  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 丢进去,把参数绑上去,它帮你执行完,还能帮你取出结果。

举个最直白的比喻:

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

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结构体的定义

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;

示例:

//假设已经连接好了
// 初始化语句
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为:

| 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 的第一步。

MYSQL* mysql_init(MYSQL* mysql);

参数:

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

返回值:

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

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

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_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

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

void mysql_close(MYSQL *mysql);

参数:

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

返回值:

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

mysql_error

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

const char *mysql_error(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

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

示例

#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*连接句柄设置参数,比如超时、字符集、重连等。

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

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

int mysql_set_character_set(MYSQL *mysql, const char *csname);

参数:

  • mysql:连接句柄。

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

返回值:

  • 0:成功。

  • 非 0:失败。

mysql_get_character_set_info

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

const MYSQL_CHARSET_INFO *mysql_get_character_set_info(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

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

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

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

int mysql_ping(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

  • 返回 0 表示连接有效

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

mysql_thread_id

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

unsigned long mysql_thread_id(MYSQL *mysql);

参数:

  • mysql:连接句柄。

返回值:

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

mysql_get_client_info

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

const char *mysql_get_client_info(void);

mysql_get_server_info

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

const char *mysql_get_server_info(MYSQL *mysql);

示例:

#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";}// 打印线程 IDstd::cout << "Connection thread ID: " << mysql_thread_id(conn) << "\n";mysql_close(conn);return 0;
}

mysql_shutdown

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

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 服务器的缓存或状态。常用于刷新日志、权限或其他内部状态。

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 服务器中的线程。此操作需要管理员权限。

int mysql_kill(MYSQL *mysql, unsigned long thread_id);

参数说明:

  • mysql:已连接的 MYSQL 句柄。

  • thread_id:要终止的线程 ID。

返回值:

  • 成功:返回 0。

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

mysql_stat

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

char *mysql_stat(MYSQL *mysql);

参数:

  • mysql:已连接的 MYSQL 句柄。

返回值:

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

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

示例:

#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 为 5if (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 等)。

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 语句。

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 注入攻击

示例:

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

 拼接结果是:

INSERT INTO users(name) VALUES('O'Reilly')

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

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

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

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

函数原型:

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 * 参数。更推荐使用

unsigned long mysql_real_escape_string(MYSQL *mysql,char *to,const char *from,unsigned long length);

参数:

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

  • to:目标缓冲区。

  • from:原始字符串。

  • length:原始字符串长度。

返回值:

  • 返回写入 to 的字节数。

示例:

#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() 来获取“那条语句”的结果。

MYSQL_RES *mysql_store_result(MYSQL *mysql);

参数:

  • mysql:已连接的 MySQL 句柄。

返回值

  • 成功:返回 MYSQL_RES * 指针。

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

使用流程

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

mysql_use_result

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

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 查询 → 因为结果还“留”在服务器端。

#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() 获取的结果集,避免内存泄漏。

void mysql_free_result(MYSQL_RES *result);

参数

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

第二组:逐行读取数据

mysql_fetch_row

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

MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

参数

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

返回值

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

  • 结束或无数据:返回 NULL

mysql_fetch_lengths

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

unsigned long *mysql_fetch_lengths(MYSQL_RES *result);

参数

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

返回值

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

  • 失败:返回 NULL

示例:

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

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

unsigned int mysql_num_fields(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

  • 字段总数(列数)。

mysql_fetch_field

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

MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result);

参数

  • result:结果集指针。

返回值

  • 成功:返回 MYSQL_FIELD*

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

mysql_fetch_fields

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

MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

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

  • 失败:返回 NULL

mysql_fetch_field_direct

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

MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr);

参数

  • res:结果集指针。

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

返回值

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

mysql_field_seek

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

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() 返回到当前位置。

MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res);

参数

  • res:结果集指针。

返回值

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

示例:

| id | title     | artist      |
| -- | --------- | ----------- |
| 1  | Yesterday | The Beatles |
| 2  | Hello     | Adele       |
| 3  | Imagine   | John Lennon |
#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语句结构)
第二步:告诉它你要用哪些数据(填入参数)

示例:

 假设有一条插入语句:

INSERT INTO users (name, age) VALUES ('Alice', 25);

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

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

问题:

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

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

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

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

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

安全(预处理):

SELECT * FROM users WHERE username = ?

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

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

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

适用场景:

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

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

第一组:初始化与准备

mysql_stmt_init

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

MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);

参数

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

返回值

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

  • 失败:返回 NULL

mysql_stmt_prepare

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

int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);

参数

  • stmt:预处理语句对象。

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

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

返回值

  • 成功:返回 0。

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

示例:

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

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

int mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind);

参数

  • stmt:预处理语句对象。

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

返回值

  • 成功:返回 0。

  • 失败:返回非零。

mysql_stmt_execute

执行已准备好的语句。

int mysql_stmt_execute(MYSQL_STMT *stmt);

参数

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

返回值

  • 成功:返回 0。

  • 失败:返回非零。

示例:

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

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

int mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);

参数

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

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

返回值

  • 成功:返回 0。

  • 失败:返回非零。

mysql_stmt_fetch

逐行获取结果。

int mysql_stmt_fetch(MYSQL_STMT *stmt);

参数

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

返回值

  • 成功:返回 0。

  • 没有更多行:返回 MYSQL_NO_DATA

  • 错误:返回 1

示例:

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

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

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

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

MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt);

参数

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

返回值

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

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

用途

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

mysql_stmt_param_metadata

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

MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt);

参数

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

返回值

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

  • 无参数或失败:返回 NULL

示例:

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 = 3Column 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() 之后使用)

my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt);

参数

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

返回值

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

  • 失败:返回 0。

示例:

mysql_stmt_store_result(stmt);
my_ulonglong row_count = mysql_stmt_num_rows(stmt);

mysql_stmt_data_seek

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

void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset);

参数

  • stmt:语句句柄

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

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: Yesterdaymysql_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

int mysql_stmt_free_result(MYSQL_STMT *stmt);

参数

  • stmt:语句句柄

返回值

  • 成功:返回 0

  • 失败:返回非零

mysql_stmt_free_result(stmt);调用时机:在完成数据读取后及时释放内存。

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

mysql_stmt_attr_set

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

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

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

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 表示“不更新”。

// 设置属性:开启字段最大长度自动更新
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

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

unsigned int mysql_stmt_errno(MYSQL_STMT *stmt);

mysql_stmt_error

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

const char *mysql_stmt_error(MYSQL_STMT *stmt);

mysql_stmt_reset

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

int mysql_stmt_reset(MYSQL_STMT *stmt);

mysql_stmt_close

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

int mysql_stmt_close(MYSQL_STMT *stmt);

示例:

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 元,不能出现张三的钱扣了但李四没收到的情况。

示例:

-- 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 执行后统一提交或撤销(比如钱转了一半不能让数据库写进去),就需要关闭自动提交,手动控制事务。

int mysql_autocommit(MYSQL *mysql, my_bool mode);

参数:

  • mysql:连接句柄。

  • mode

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

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

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

mysql_autocommit(conn, 0); // 相当于 START TRANSACTION;

mysql_commit

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

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

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

int mysql_commit(MYSQL *mysql);

返回值:

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

注意:

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

示例:

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

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() 的“反操作”:撤销当前事务中的所有修改。如果你在执行多条语句后发现某一步出错,就可以调用它,防止错误写入数据库。

int mysql_rollback(MYSQL *mysql);

返回值:

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

示例:

// 订单支付逻辑
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),比如:

SELECT * FROM table1; SELECT * FROM table2;

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

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

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

mysql_next_result

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

比如执行的是:

SELECT * FROM users; SELECT * FROM songs;

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

int mysql_next_result(MYSQL *mysql);

返回值:

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

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

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

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);  // 恢复自动提交模式

相关文章:

  • GCC编译器安装详细说明(举例arm-2013q3)
  • 神经网络之训练的艺术:反向传播与常见问题解决之道
  • 移动应用开发:自定义 View 处理大量数据的性能与交互优化方案
  • 高等数学第五章---定积分(§5.3定积分的计算方法)
  • 深度解析:2D 写实交互数字人 —— 开启智能交互新时代
  • React 中集成 Ant Design 组件库:提升开发效率与用户体验
  • 深度剖析:可视化如何重塑驾驶舱信息交互模式
  • ES6/ES11知识点 续四
  • Spring Web MVC————入门(1)
  • 二、Hadoop狭义和广义的理解
  • 宏电全新升级单北斗5G电力DTU,为每一公里电力线路注入可靠连接
  • springboot微服务连接nacos超时
  • SMT贴片钢网精密设计与制造要点解析
  • openEuler22.03 环境编译安装 PostgreSQL 15.6 + PostGIS 3.4.1
  • 【计算机网络】TCP/IP四层模型是什么?与OSI七层模型哪些区别?
  • Nginx安全防护与HTTPS部署
  • AI大模型驱动的智能座舱研发体系重构
  • 金升阳科技:配套AC/DC砖类电源的高性能滤波器
  • 如何开始使用 Blender:Blender 3D 初学者指南和简介 怎么下载格式模型
  • 制造企业PLM系统成本基准:2025年预算分配与资源成本率的5种优化模型
  • 湖北十堰市委副秘书长管聪履新丹江口市代市长
  • 俄乌交换205名被俘人员,俄方人员已抵达白俄罗斯
  • 上海市委常委会扩大会议传达学习习近平总书记考察上海重要讲话和在部分省区市“十五五”时期经济社会发展座谈会上的重要讲话精神
  • “五一”假期全社会跨区域人员流动量超14.65亿人次
  • 世界哮喘日丨张旻:哮喘的整体诊断率不足三成,吸入治疗是重要治疗手段
  • 巴菲特股东大会精华版:批评拿贸易当武器,宣布年底交班