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; // 插入操作返回的自增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;
示例:
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; // 若为无符号整数类型,则此字段为 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
表有两个字段: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_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
表有两个字段: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_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]的结果是:
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";
}
// 打印线程 ID
std::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 为 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 命令(如 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 = 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()
之后使用)
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: 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
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); // 恢复自动提交模式