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_init 、mysql_query 等 |
/usr/include/mysql/mysql_version.h | 定义 MySQL 版本号和宏,例如 MYSQL_VERSION_ID |
/usr/include/mysql/mysql_com.h | 定义客户端通信协议常量与结构,如 MYSQL 、MYSQL_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_init → mysql_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 语句。
作用:
-
输入参数绑定:当执行
INSERT
、UPDATE
等操作时,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
表有两个字段:id
和 name
。我们通过 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
表有两个字段:id
和 name
。我们将通过 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(¶m, 0, sizeof(param));
param.buffer_type = MYSQL_TYPE_LONG;
param.buffer = &user_id;
mysql_stmt_bind_param(stmt, ¶m);// 执行语句
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 命令(如 SELECT
、INSERT
、UPDATE
等)。
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
:预处理语句对象。 -
bind
:MYSQL_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 每执行一条
INSERT
、UPDATE
、DELETE
就会立即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); // 恢复自动提交模式