MySQL 提供了一个用于 C/C++ 的 API,称为 MySQL Connector/C。该 API 允许通过 C/C++ 程序与 MySQL 数据库进行交互。
函数名称 | 参数 | 返回值 | 描述 |
---|---|---|---|
mysql_init | MYSQL *mysql | MYSQL * | 初始化一个 MySQL 对象,用于连接 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 数据库服务器。 |
mysql_close | MYSQL *mysql | void | 关闭与 MySQL 服务器的连接。 |
mysql_query | MYSQL *mysql, const char *query | int | 执行一条 SQL 查询。返回 0 表示成功,非 0 表示失败。 |
mysql_store_result | MYSQL *mysql | MYSQL_RES * | 检索完整的结果集。返回指向结果集的指针,或者失败时返回 NULL。 |
mysql_free_result | MYSQL_RES *result | void | 释放结果集的内存。 |
mysql_fetch_row | MYSQL_RES *result | MYSQL_ROW | 从结果集中获取下一行。返回表示行的数组,或者没有更多数据时返回 NULL。 |
mysql_affected_rows | MYSQL *mysql | my_ulonglong | 返回最近执行的 SQL 语句影响的行数。 |
mysql_num_rows | MYSQL_RES *result | my_ulonglong | 返回结果集中行的数量。 |
mysql_num_fields | MYSQL_RES *result | unsigned int | 返回结果集中的字段数。 |
mysql_error | MYSQL *mysql | const char * | 返回最近一次 MySQL 操作的错误消息字符串。 |
mysql_real_escape_string | MYSQL *mysql, char *to, const char *from, unsigned long length | unsigned long | 转义字符串中的特殊字符,使其可以安全地用于 SQL 语句中。 |
mysql_commit | MYSQL *mysql | int | 提交当前事务。返回 0 表示成功,非 0 表示失败。 |
mysql_rollback | MYSQL *mysql | int | 回滚当前事务。返回 0 表示成功,非 0 表示失败。 |
mysql_set_character_set | MYSQL *mysql, const char *charset | int | 设置当前连接使用的字符集。返回 0 表示成功,非 0 表示失败。 |
mysql_autocommit | MYSQL *mysql, my_bool mode | int | 开启或关闭自动提交功能。传入 0 表示关闭,1 表示开启。 |
mysql_stmt_init | MYSQL *mysql | MYSQL_STMT * | 初始化一个预处理语句句柄。 |
mysql_stmt_prepare | MYSQL_STMT *stmt, const char *query, unsigned long length | int | 预处理一个 SQL 查询。返回 0 表示成功,非 0 表示失败。 |
mysql_stmt_bind_param | MYSQL_STMT *stmt, MYSQL_BIND *bind | int | 绑定参数到预处理语句。 |
mysql_stmt_bind_result | MYSQL_STMT *stmt, MYSQL_BIND *bind | int | 绑定结果变量到预处理语句。 |
mysql_stmt_execute | MYSQL_STMT *stmt | int | 执行一个预处理语句。返回 0 表示成功,非 0 表示失败。 |
mysql_stmt_fetch | MYSQL_STMT *stmt | int | 获取执行结果中的下一行。返回 0 表示成功,非 0 表示失败或无更多行。 |
mysql_stmt_close | MYSQL_STMT *stmt | int | 关闭一个预处理语句句柄。返回 0 表示成功,非 0 表示失败。 |
mysql_stmt_result_metadata | MYSQL_STMT *stmt | MYSQL_RES * | 获取一个预处理语句执行后返回结果的元数据。返回结果集结构指针,或者 NULL 表示没有元数据。 |
mysql_stmt_num_rows | MYSQL_STMT *stmt | my_ulonglong | 获取结果集中返回的行数。 |
mysql_stmt_store_result | MYSQL_STMT *stmt | int | 将完整的结果集存储在客户端内存中。返回 0 表示成功,非 0 表示失败。 |
mysql_stmt_free_result | MYSQL_STMT *stmt | void | 释放预处理语句的结果集。 |
mysql_stmt_reset | MYSQL_STMT *stmt | int | 重置预处理语句,使其可以重新执行。返回 0 表示成功,非 0 表示失败。 |
说明
- 返回值为
int
类型的函数:通常 0 表示成功,非 0 表示失败。 - 返回值为指针的函数:通常返回一个指向对象的指针,
NULL
表示失败。 MYSQL_BIND
是一个用于绑定参数或结果的结构,通常用于预处理语句(mysql_stmt
)相关的函数中。
1. 初始化 MySQL 连接
函数 mysql_init
用于初始化 MySQL 连接。
#include <mysql/mysql.h>
MYSQL *mysql_init(MYSQL *mysql);
-
参数:
mysql
:指向 MYSQL 结构体的指针,可以为NULL
。
-
返回值:
- 成功:返回一个 MYSQL 结构体指针。
- 失败:返回
NULL
。
-
示例:
-
MYSQL *conn;
conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
exit(EXIT_FAILURE);
}
2. 连接数据库
使用 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 结构体指针。host
: 数据库服务器地址(如 "localhost" 或 IP 地址)。user
: 数据库用户名。passwd
: 数据库用户密码。db
: 需要连接的数据库名。port
: MySQL 服务器的端口号,通常为 3306。unix_socket
: 本地 Unix Socket,若未使用可设为NULL
。client_flag
: 客户端标志位,通常为 0。
-
返回值:
- 成功:返回 MYSQL 结构体指针。
- 失败:返回
NULL
。
-
示例:
-
if (mysql_real_connect(conn, "localhost", "user", "password", "testdb", 0, NULL, 0) == NULL) {
fprintf(stderr, "mysql_real_connect() failed\n");
mysql_close(conn);
exit(EXIT_FAILURE);
}
3. 执行 SQL 查询
函数 mysql_query
用于执行 SQL 语句。
int mysql_query(MYSQL *mysql, const char *query);
-
参数:
mysql
: 已连接的 MYSQL 结构体指针。query
: 要执行的 SQL 查询字符串。
-
返回值:
- 成功:返回 0。
- 失败:返回非 0 值。
-
示例:
-
if (mysql_query(conn, "CREATE TABLE test (id INT, name VARCHAR(20))")) {
fprintf(stderr, "CREATE TABLE failed. Error: %s\n", mysql_error(conn));
}
4. 获取查询结果
使用 mysql_store_result
获取查询的结果集,并使用 mysql_fetch_row
获取每一行的记录。
MYSQL_RES *mysql_store_result(MYSQL *mysql);
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
-
参数:
mysql_store_result
: 返回查询结果的指针,若查询不返回数据则返回NULL
。mysql_fetch_row
: 返回结果集中的下一行数据,若无更多数据返回NULL
。
-
示例:
-
if (mysql_query(conn, "SELECT id, name FROM test")) {
fprintf(stderr, "SELECT failed. Error: %s\n", mysql_error(conn));
}MYSQL_RES *result = mysql_store_result(conn);
if (result == NULL) {
fprintf(stderr, "mysql_store_result() failed. Error: %s\n", mysql_error(conn));
}int num_fields = mysql_num_fields(result);
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
for(int i = 0; i < num_fields; i++) {
printf("%s ", row[i] ? row[i] : "NULL");
}
printf("\n");
}
mysql_free_result(result);
5. 关闭 MySQL 连接
使用 mysql_close
关闭与 MySQL 的连接。
void mysql_close(MYSQL *mysql);
-
参数:
mysql
: 连接的 MYSQL 结构体指针。
-
示例:
-
mysql_close(conn);
6. 错误处理
通过 mysql_error
获取最近的错误信息。
const char *mysql_error(MYSQL *mysql);
-
参数:
mysql
: 连接的 MYSQL 结构体指针。
-
返回值:
- 返回一个 C 字符串,包含错误信息。
-
示例:
-
fprintf(stderr, "Error: %s\n", mysql_error(conn));
完整示例
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
// 初始化 MySQL 连接
conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
exit(EXIT_FAILURE);
}
// 连接到数据库
if (mysql_real_connect(conn, "localhost", "user", "password", "testdb", 0, NULL, 0) == NULL) {
fprintf(stderr, "mysql_real_connect() failed\n");
mysql_close(conn);
exit(EXIT_FAILURE);
}
// 执行 SQL 查询
if (mysql_query(conn, "SELECT id, name FROM test")) {
fprintf(stderr, "SELECT failed. Error: %s\n", mysql_error(conn));
mysql_close(conn);
exit(EXIT_FAILURE);
}
// 获取结果集
res = mysql_store_result(conn);
if (res == NULL) {
fprintf(stderr, "mysql_store_result() failed. Error: %s\n", mysql_error(conn));
mysql_close(conn);
exit(EXIT_FAILURE);
}
// 输出查询结果
int num_fields = mysql_num_fields(res);
while ((row = mysql_fetch_row(res))) {
for(int i = 0; i < num_fields; i++) {
printf("%s ", row[i] ? row[i] : "NULL");
}
printf("\n");
}
// 释放结果集
mysql_free_result(res);
// 关闭 MySQL 连接
mysql_close(conn);
exit(EXIT_SUCCESS);
}
其他常用 API 函数
mysql_affected_rows
: 获取查询影响的行数。mysql_num_fields
: 获取结果集中字段的数量。mysql_field_count
: 获取查询的字段数。mysql_insert_id
: 获取插入操作后生成的自增 ID。
7. 事务处理
MySQL 支持事务,可以使用 mysql_autocommit
、mysql_commit
和 mysql_rollback
来手动管理事务。
自动提交模式
int mysql_autocommit(MYSQL *mysql, my_bool mode);
-
参数:
mysql
: 已连接的 MYSQL 结构体指针。mode
: 设置是否自动提交,1
为启用自动提交,0
为关闭自动提交。
-
返回值:
- 成功返回 0,失败返回非 0 值。
-
示例:
-
mysql_autocommit(conn, 0); // 关闭自动提交
提交事务
int mysql_commit(MYSQL *mysql);
-
参数:
mysql
: 已连接的 MYSQL 结构体指针。
-
返回值:
- 成功返回 0,失败返回非 0 值。
-
示例:
-
if (mysql_commit(conn)) {
fprintf(stderr, "Commit failed. Error: %s\n", mysql_error(conn));
}
回滚事务
int mysql_rollback(MYSQL *mysql);
-
参数:
mysql
: 已连接的 MYSQL 结构体指针。
-
返回值:
- 成功返回 0,失败返回非 0 值。
-
示例:
-
if (mysql_rollback(conn)) {
fprintf(stderr, "Rollback failed. Error: %s\n", mysql_error(conn));
}
8. 预处理语句 (Prepared Statements)
预处理语句用于执行重复的 SQL 语句或提高安全性,避免 SQL 注入。主要函数有 mysql_stmt_init
、mysql_stmt_prepare
、mysql_stmt_execute
等。
初始化预处理语句
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);
- 参数:
mysql
: 已连接的 MYSQL 结构体指针。
- 返回值:
- 成功返回
MYSQL_STMT
结构体指针,失败返回NULL
。
- 成功返回
准备 SQL 语句
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);
-
参数:
stmt
:mysql_stmt_init
返回的预处理语句结构体指针。query
: SQL 查询语句。length
: SQL 查询语句的长度。
-
返回值:
- 成功返回 0,失败返回非 0 值。
执行预处理语句
int mysql_stmt_execute(MYSQL_STMT *stmt);
-
参数:
stmt
: 已准备的预处理语句结构体指针。
-
返回值:
- 成功返回 0,失败返回非 0 值。
-
示例:
-
MYSQL_STMT *stmt;
stmt = mysql_stmt_init(conn);
if (!stmt) {
fprintf(stderr, "mysql_stmt_init() failed\n");
}const char *query = "INSERT INTO test (id, name) VALUES (?, ?)";
if (mysql_stmt_prepare(stmt, query, strlen(query))) {
fprintf(stderr, "mysql_stmt_prepare() failed. Error: %s\n", mysql_error(conn));
}// 设置参数并执行预处理语句(略)
if (mysql_stmt_execute(stmt)) {
fprintf(stderr, "mysql_stmt_execute() failed. Error: %s\n", mysql_error(conn));
}mysql_stmt_close(stmt);
9. 获取结果字段的元数据
使用 mysql_fetch_fields
可以获取结果集中各字段的元数据。
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result);
-
参数:
result
: 结果集指针。
-
返回值:
- 返回字段数组。
-
示例:
-
MYSQL_RES *result = mysql_store_result(conn);
MYSQL_FIELD *fields = mysql_fetch_fields(result);for (int i = 0; i < mysql_num_fields(result); i++) {
printf("Field %d: %s\n", i, fields[i].name);
}
10. 大数据集处理
使用 mysql_use_result
可以处理大数据集,避免一次性将所有数据加载到内存中。
MYSQL_RES *mysql_use_result(MYSQL *mysql);
-
参数:
mysql
: 连接的 MYSQL 结构体指针。
-
返回值:
- 成功返回结果集指针,失败返回
NULL
。
- 成功返回结果集指针,失败返回
-
示例:
-
MYSQL_RES *result = mysql_use_result(conn);
MYSQL_ROW row;while ((row = mysql_fetch_row(result))) {
// 处理每一行数据
}mysql_free_result(result);
11. 多线程支持
MySQL C API 是线程安全的,但在多线程环境下,需要使用以下两个函数:
初始化多线程环境
int mysql_thread_init(void);
结束多线程环境
void mysql_thread_end(void);
在多线程程序开始时,调用 mysql_thread_init
,在结束时调用 mysql_thread_end
,确保多线程环境的正确使用。
12. 字符集操作
MySQL 提供了一些函数来处理字符集,确保数据库与应用程序之间的数据正确编码。
设置客户端字符集
int mysql_set_character_set(MYSQL *mysql, const char *csname);
-
参数:
mysql
: 连接的 MYSQL 结构体指针。csname
: 字符集名称(如 "utf8")。
-
返回值:
- 成功返回 0,失败返回非 0 值。
-
示例:
-
if (mysql_set_character_set(conn, "utf8")) {
fprintf(stderr, "Failed to set character set to utf8. Error: %s\n", mysql_error(conn));
}
获取当前客户端字符集
const char *mysql_character_set_name(MYSQL *mysql);
-
返回值:
- 返回当前客户端字符集的名称。
-
示例:
-
printf("Client character set: %s\n", mysql_character_set_name(conn));
13. 错误处理及诊断
MySQL 提供了丰富的错误处理和诊断函数,如 mysql_errno
、mysql_sqlstate
、mysql_warning_count
。
获取错误代码
unsigned int mysql_errno(MYSQL *mysql);
获取 SQL 状态码
const char *mysql_sqlstate(MYSQL *mysql);
获取警告数量
unsigned int mysql_warning_count(MYSQL *mysql);
demo:
printf("MySQL Error No: %u\n", mysql_errno(conn));
printf("MySQL SQLState: %s\n", mysql_sqlstate(conn));
printf("MySQL Warning Count: %u\n", mysql_warning_count(conn));
14. 断线重连
MySQL 提供了自动重连功能,通过以下代码可以启用断线重连:
my_bool reconnect = 1;
mysql_options(conn, MYSQL_OPT_RECONNECT, &reconnect);
15. 关闭 MySQL 连接
别忘了每次操作结束后关闭连接:
mysql_close(conn);
16. 预处理语句 (Prepared Statements) 高级操作
预处理语句 (MYSQL_STMT
) 是处理动态查询、重复执行 SQL 操作和防止 SQL 注入的重要机制。通过预处理语句,可以提高性能并增强 SQL 的安全性。
初始化预处理语句
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);
-
参数:
stmt
: 初始化的MYSQL_STMT
结构体指针。query
: SQL 查询语句。length
: SQL 查询语句的长度(以字节为单位)。
-
返回值:
- 成功返回 0,失败返回非 0 值。
绑定参数
在执行预处理语句前,必须将参数绑定到 SQL 语句中。使用 mysql_stmt_bind_param
来绑定参数。
int mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind);
-
参数:
stmt
: 预处理语句的结构体指针。bind
: 指向包含要绑定参数的MYSQL_BIND
结构体数组。
-
返回值:
- 成功返回 0,失败返回非 0 值。
MYSQL_BIND 结构体
MYSQL_BIND
用于定义输入或输出参数。其成员包括:
buffer
: 指向数据的指针。buffer_type
: 参数的数据类型,如MYSQL_TYPE_LONG
、MYSQL_TYPE_STRING
。is_null
: 指向标识参数是否为NULL
的指针。length
: 指向保存数据长度的指针。
示例:绑定参数
MYSQL_STMT *stmt;
MYSQL_BIND bind[2];
int id;
char name[20];
stmt = mysql_stmt_init(conn);
const char *query = "INSERT INTO test (id, name) VALUES (?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
memset(bind, 0, sizeof(bind));
// 绑定 ID 参数
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&id;
// 绑定 Name 参数
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = name;
bind[1].buffer_length = sizeof(name);
// 绑定参数
mysql_stmt_bind_param(stmt, bind);
// 设置参数值
id = 1;
strcpy(name, "Alice");
// 执行预处理语句
mysql_stmt_execute(stmt);
mysql_stmt_close(stmt);
执行预处理语句
int mysql_stmt_execute(MYSQL_STMT *stmt);
-
参数:
stmt
: 已准备的预处理语句结构体指针。
-
返回值:
- 成功返回 0,失败返回非 0 值。
绑定结果
查询预处理语句需要绑定结果变量到 MYSQL_BIND
结构体中。
int mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);
-
参数:
stmt
: 预处理语句的结构体指针。bind
: 指向包含结果数据的MYSQL_BIND
结构体数组。
-
返回值:
- 成功返回 0,失败返回非 0 值。
示例:绑定查询结果
MYSQL_STMT *stmt;
MYSQL_BIND bind[2];
int id;
char name[20];
stmt = mysql_stmt_init(conn);
const char *query = "SELECT id, name FROM test WHERE id = ?";
mysql_stmt_prepare(stmt, query, strlen(query));
// 绑定参数
MYSQL_BIND param[1];
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (char *)&id;
mysql_stmt_bind_param(stmt, param);
id = 1;
mysql_stmt_execute(stmt);
// 绑定结果
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&id;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = name;
bind[1].buffer_length = sizeof(name);
mysql_stmt_bind_result(stmt, bind);
// 获取结果
while (mysql_stmt_fetch(stmt) == 0) {
printf("ID: %d, Name: %s\n", id, name);
}
mysql_stmt_close(stmt);
获取结果行
int mysql_stmt_fetch(MYSQL_STMT *stmt);
-
参数:
stmt
: 预处理语句的结构体指针。
-
返回值:
0
:成功获取一行结果。MYSQL_NO_DATA
:没有更多数据。- 其他非零值:发生错误。
释放预处理语句
int mysql_stmt_close(MYSQL_STMT *stmt);
-
参数:
stmt
: 预处理语句的结构体指针。
-
返回值:
- 成功返回 0,失败返回非 0 值。
17. 获取预处理语句的元数据
可以通过 mysql_stmt_param_metadata
和 mysql_stmt_result_metadata
函数获取预处理语句的参数和结果元数据。
获取参数元数据
MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt);
- 参数:
stmt
: 预处理语句的结构体指针。
- 返回值:
- 返回结果集,包含预处理语句参数的元数据。
获取结果元数据
MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt);
-
参数:
stmt
: 预处理语句的结构体指针。
-
返回值:
- 返回结果集,包含预处理语句结果集的元数据。
-
示例:
-
MYSQL_RES *result_metadata = mysql_stmt_result_metadata(stmt);
if (result_metadata) {
int num_fields = mysql_num_fields(result_metadata);
printf("Number of result fields: %d\n", num_fields);
mysql_free_result(result_metadata);
}
18. 事务和预处理语句的结合
通过预处理语句和事务的结合,可以确保多条 SQL 语句要么全部成功,要么全部失败。
示例:事务与预处理语句结合
mysql_autocommit(conn, 0); // 关闭自动提交
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *query = "INSERT INTO test (id, name) VALUES (?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
MYSQL_BIND bind[2];
int id = 1;
char name[20] = "Alice";
// 绑定参数
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&id;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = name;
bind[1].buffer_length = sizeof(name);
mysql_stmt_bind_param(stmt, bind);
if (mysql_stmt_execute(stmt)) {
mysql_rollback(conn); // 执行失败,回滚
} else {
mysql_commit(conn); // 执行成功,提交事务
}
mysql_stmt_close(stmt);
mysql_autocommit(conn, 1); // 恢复自动提交
19. 批量插入
使用 mysql_stmt_execute
可以进行批量插入,通过修改绑定的数据,反复执行 mysql_stmt_execute
。
- 示例:
MYSQL_STMT *stmt;
stmt = mysql_stmt_init(conn);
const char *query = "INSERT INTO test (id, name) VALUES (?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
MYSQL_BIND bind[2];
int id;
char name[20];
memset(bind, 0, sizeof(bind));
// 绑定参数
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&id;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = name;
bind[1].buffer_length = sizeof(name);
mysql_stmt_bind_param(stmt, bind);
// 批量插入数据
for (int i = 1; i <= 10; i++) {
id = i;
sprintf(name, "Name%d", i);
mysql_stmt_execute(stmt);
}
mysql_stmt_close(stmt);
20. 存储过程
MySQL 支持通过预处理语句调用存储过程。
const char *query = "CALL procedure_name(?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
通过绑定参数和执行来调用存储过程,和普通预处理语句非常类似。
21. 多结果集处理 (Multiple Result Sets)
在某些情况下(例如存储过程或执行多个查询),MySQL 返回多个结果集。MySQL C API 提供了一些函数来处理这些结果集。
检测是否有更多的结果集
int mysql_more_results(MYSQL *mysql);
-
参数:
mysql
: 已连接的MYSQL
结构体指针。
-
返回值:
- 如果有更多的结果集返回非 0 值,否则返回 0。
移动到下一个结果集
int mysql_next_result(MYSQL *mysql);
-
参数:
mysql
: 已连接的MYSQL
结构体指针。
-
返回值:
- 成功返回 0,出错返回非 0 值。
示例:处理多结果集
if (mysql_query(conn, "CALL my_procedure();")) {
fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
}
do {
MYSQL_RES *result = mysql_store_result(conn);
if (result) {
// 处理当前结果集
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
printf("Row: %s\n", row[0]);
}
mysql_free_result(result);
}
} while (mysql_more_results(conn) && mysql_next_result(conn) == 0);
22. 多查询执行 (Multiple Queries)
MySQL 支持一次性执行多条 SQL 语句。通过 mysql_query
或 mysql_real_query
执行包含多条 SQL 语句的查询,结果集可使用 mysql_store_result
获取。
int mysql_query(MYSQL *mysql, const char *query);
int mysql_real_query(MYSQL *mysql, const char *query, unsigned long length);
-
参数:
query
: 包含多条 SQL 语句的字符串。
-
返回值:
- 成功返回 0,失败返回非 0 值。
示例:多查询执行
const char *query = "SELECT * FROM table1; SELECT * FROM table2;";
if (mysql_query(conn, query)) {
fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
}
do {
MYSQL_RES *result = mysql_store_result(conn);
if (result) {
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
printf("Row: %s\n", row[0]);
}
mysql_free_result(result);
}
} while (mysql_more_results(conn) && mysql_next_result(conn) == 0);
23. 大数据操作
对于非常大的数据集,可以使用流式读取或写入以避免内存不足。mysql_use_result
允许逐行处理结果集,而不是将整个结果集载入内存。
逐行获取大数据集
MYSQL_RES *mysql_use_result(MYSQL *mysql);
- 返回值:
- 返回结果集指针,用于流式读取。
示例:逐行读取结果
MYSQL_RES *result = mysql_use_result(conn);
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
printf("Row: %s\n", row[0]);
}
mysql_free_result(result);
24. 异步查询
MySQL C API 的传统函数都是同步的。对于某些场景,异步查询可以提高性能。虽然原生 C API 不提供完全异步的查询接口,但可以通过分离连接的线程执行来实现异步操作。也可以使用 mysql_poll
来检查某些非阻塞操作的状态。
示例:异步查询模型
通常通过创建新线程执行查询,然后主线程处理其他任务,最终等待查询完成。
void *run_query(void *arg) {
MYSQL *conn = (MYSQL *)arg;
mysql_query(conn, "SELECT * FROM test");
// 处理查询结果
return NULL;
}
int main() {
MYSQL *conn = mysql_init(NULL);
mysql_real_connect(conn, "host", "user", "password", "database", 0, NULL, 0);
pthread_t thread;
pthread_create(&thread, NULL, run_query, conn);
// 主线程可以处理其他任务
// ...
pthread_join(thread, NULL);
mysql_close(conn);
return 0;
}
25. SSL 安全连接
MySQL C API 支持 SSL 加密连接,通过 mysql_ssl_set
函数可以配置 SSL 相关的参数。
设置 SSL 连接
int mysql_ssl_set(MYSQL *mysql,
const char *key,
const char *cert,
const char *ca,
const char *capath,
const char *cipher);
-
参数:
key
: 客户端私钥文件。cert
: 客户端证书文件。ca
: 受信任的 CA 文件。capath
: 受信任的 CA 文件路径。cipher
: SSL 加密算法。
-
示例:
-
MYSQL *conn = mysql_init(NULL);
mysql_ssl_set(conn, "client-key.pem", "client-cert.pem", "ca-cert.pem", NULL, NULL);
mysql_real_connect(conn, "host", "user", "password", "database", 0, NULL, CLIENT_SSL);
26. 存储过程的输出参数处理
当调用存储过程并处理输出参数时,需使用预处理语句并绑定输出参数到结果集中。与普通预处理语句不同,输出参数需要绑定为 MYSQL_BIND
的 is_null
和 length
字段。
示例:存储过程输出参数
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *query = "CALL my_procedure(?, ?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
MYSQL_BIND bind[3];
memset(bind, 0, sizeof(bind));
// 绑定输入参数
int input = 5;
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&input;
// 绑定输出参数
int output;
unsigned long length;
my_bool is_null;
bind[1].buffer_type = MYSQL_TYPE_LONG;
bind[1].buffer = (char *)&output;
bind[1].length = &length;
bind[1].is_null = &is_null;
mysql_stmt_bind_param(stmt, bind);
mysql_stmt_execute(stmt);
// 获取输出参数
mysql_stmt_fetch(stmt);
printf("Output: %d\n", output);
mysql_stmt_close(stmt);
27. 自定义错误处理和诊断
除了通过 mysql_error
获取错误信息,还可以使用 mysql_errno
和 mysql_sqlstate
进一步诊断错误。
28. 自定义连接超时和重试机制
可以使用 mysql_options
设置连接超时、重试次数等高级选项。
设置连接超时
int timeout = 10; // 设置超时时间为 10 秒
mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
自动重连
my_bool reconnect = 1;
mysql_options(conn, MYSQL_OPT_RECONNECT, &reconnect);
练习项目:图书馆管理系统
项目设计:图书馆管理系统
项目简介:
我们将实现一个 图书馆管理系统,通过控制台界面与用户交互,支持对用户、图书、借阅信息的全面管理。系统还会包括一些高级功能,比如:
- 用户角色权限管理(管理员和普通用户)
- 预定与借阅图书的操作
- 图书库存检查与批量入库
- 日志记录(操作记录)
- 数据持久化(本地数据库)
功能需求:
- 用户管理
- 注册用户,分为管理员和普通用户,管理员具有更多权限。
- 图书管理
- 添加图书、编辑图书信息、删除图书。
- 借阅与归还
- 用户可以借阅或归还图书,管理员可以查看借阅记录。
- 图书预定
- 如果图书已被借出,用户可以预定,待图书归还后自动通知。
- 库存管理
- 管理员可以查看库存不足的图书,进行批量入库操作。
- 日志管理
- 系统记录所有操作日志,供管理员查看。
数据库设计:
CREATE DATABASE library_management;
USE library_management;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
role ENUM('admin', 'user') NOT NULL
);
CREATE TABLE books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
author VARCHAR(100),
stock INT DEFAULT 0
);
CREATE TABLE borrow_records (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
book_id INT,
borrow_date DATE,
return_date DATE,
status ENUM('borrowed', 'returned') DEFAULT 'borrowed',
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (book_id) REFERENCES books(id)
);
CREATE TABLE reservations (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
book_id INT,
reservation_date DATE,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (book_id) REFERENCES books(id)
);
项目结构:
library_management/
│
├── include/
│ ├── database.h # 数据库操作头文件
│ ├── user.h # 用户管理头文件
│ ├── book.h # 图书管理头文件
│ ├── borrow.h # 借阅管理头文件
│ ├── reservation.h # 预定管理头文件
├── src/
│ ├── database.c # 数据库操作实现
│ ├── user.c # 用户管理实现
│ ├── book.c # 图书管理实现
│ ├── borrow.c # 借阅管理实现
│ ├── reservation.c # 预定管理实现
│ ├── main.c # 主程序入口
├── Makefile # 编译工程的 Makefile(在Windows环境下可以使用MinGW或Visual Studio)
└── README.md # 项目说明文档
技术栈:
- 开发语言:C/C++
- 数据库:MySQL(Windows 下可以安装 MySQL Server)
- 开发环境:Visual Studio 或 MinGW (GCC),支持 C/C++ 开发
- Windows 专有库:可选使用 WinAPI 实现图形界面或控制台输出的美化
第一步:设置 Windows 开发环境
1. MySQL 在 Windows 上的安装与配置
- 下载并安装 MySQL,安装 MySQL Server 和 MySQL C API 客户端库。
- 配置数据库,确保 MySQL 服务正常运行,并设置好用户权限。
2. 安装开发工具
- Visual Studio:可以直接安装 C/C++ 开发环境,集成调试、编译、运行功能。
- MinGW:如果更喜欢命令行,可以安装 MinGW,它可以在 Windows 上提供类似 Linux 的 GCC 编译环境。
第二步:数据库操作模块
我们首先编写数据库模块来支持与 MySQL 数据库的交互。由于在 Windows 下开发,可以利用 MySQL 提供的 C API 函数,与 MySQL 进行连接、执行 SQL 语句、获取查询结果等。
include/database.h
#ifndef DATABASE_H
#define DATABASE_H
#include <mysql/mysql.h>
// 初始化数据库连接
MYSQL* init_db();
// 关闭数据库连接
void close_db(MYSQL *conn);
// 执行查询并返回结果
MYSQL_RES* execute_query(MYSQL *conn, const char *query);
// 执行更新或插入操作
int execute_update(MYSQL *conn, const char *query);
#endif
src/database.c
#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>
#include "database.h"
// 初始化数据库连接
MYSQL* init_db() {
MYSQL *conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
return NULL;
}
if (mysql_real_connect(conn, "localhost", "root", "password", "library_management", 0, NULL, 0) == NULL) {
fprintf(stderr, "mysql_real_connect() failed: %s\n", mysql_error(conn));
mysql_close(conn);
return NULL;
}
return conn;
}
// 关闭数据库连接
void close_db(MYSQL *conn) {
mysql_close(conn);
}
// 执行查询并返回结果
MYSQL_RES* execute_query(MYSQL *conn, const char *query) {
if (mysql_query(conn, query)) {
fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
return NULL;
}
return mysql_store_result(conn);
}
// 执行更新或插入操作
int execute_update(MYSQL *conn, const char *query) {
if (mysql_query(conn, query)) {
fprintf(stderr, "Update failed: %s\n", mysql_error(conn));
return 0;
}
return 1;
}
第三步:用户管理模块
include/user.h
#ifndef USER_H
#define USER_H
#include <mysql/mysql.h>
// 用户登录
int user_login(MYSQL *conn, const char *username, const char *password);
// 用户注册
int user_register(MYSQL *conn, const char *username, const char *password, const char *role);
// 获取用户角色
char* get_user_role(MYSQL *conn, const char *username);
#endif
src/user.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "user.h"
// 用户登录
int user_login(MYSQL *conn, const char *username, const char *password) {
char query[256];
sprintf(query, "SELECT * FROM users WHERE username = '%s' AND password = '%s'", username, password);
MYSQL_RES *res = execute_query(conn, query);
if (mysql_num_rows(res) > 0) {
mysql_free_result(res);
return 1;
}
mysql_free_result(res);
return 0;
}
// 用户注册
int user_register(MYSQL *conn, const char *username, const char *password, const char *role) {
char query[256];
sprintf(query, "INSERT INTO users (username, password, role) VALUES ('%s', '%s', '%s')", username, password, role);
return execute_update(conn, query);
}
// 获取用户角色
char* get_user_role(MYSQL *conn, const char *username) {
char query[256];
sprintf(query, "SELECT role FROM users WHERE username = '%s'", username);
MYSQL_RES *res = execute_query(conn, query);
MYSQL_ROW row = mysql_fetch_row(res);
char *role = strdup(row[0]);
mysql_free_result(res);
return role;
}
第四步:图书管理模块
include/book.h
#ifndef BOOK_H
#define BOOK_H
#include <mysql/mysql.h>
// 添加图书
int add_book(MYSQL *conn, const char *title, const char *author, int stock);
// 获取所有图书
MYSQL_RES* get_all_books(MYSQL *conn);
// 根据书籍 ID 获取图书信息
MYSQL_RES* get_book_by_id(MYSQL *conn, int id);
// 修改图书信息
int update_book(MYSQL *conn, int id, const char *title, const char *author, int stock);
// 删除图书
int delete_book(MYSQL *conn, int id);
#endif
src/book.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "book.h"
// 添加图书
int add_book(MYSQL *conn, const char *title, const char *author, int stock) {
char query[256];
sprintf(query, "INSERT INTO books (title, author, stock) VALUES ('%s', '%s', %d)", title, author, stock);
return execute_update(conn, query);
}
// 获取所有图书
MYSQL_RES* get_all_books(MYSQL *conn) {
const char *query = "SELECT * FROM books";
return execute_query(conn, query);
}
// 根据书籍 ID 获取图书信息
MYSQL_RES* get_book_by_id(MYSQL *conn, int id) {
char query[100];
sprintf(query, "SELECT * FROM books WHERE id = %d", id);
return execute_query(conn, query);
}
// 修改图书信息
int update_book(MYSQL *conn, int id, const char *title, const char *author, int stock) {
char query[256];
sprintf(query, "UPDATE books SET title = '%s', author = '%s', stock = %d WHERE id = %d", title, author, stock, id);
return execute_update(conn, query);
}
// 删除图书
int delete_book(MYSQL *conn, int id) {
char query[100];
sprintf(query, "DELETE FROM books WHERE id = %d", id);
return execute_update(conn, query);
}
借阅管理模块
include/borrow.h
#ifndef BORROW_H
#define BORROW_H
#include <mysql/mysql.h>
// 借阅图书
int borrow_book(MYSQL *conn, int user_id, int book_id);
// 归还图书
int return_book(MYSQL *conn, int user_id, int book_id);
// 获取用户的借阅记录
MYSQL_RES* get_borrow_records(MYSQL *conn, int user_id);
#endif
src/borrow.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "borrow.h"
// 借阅图书
int borrow_book(MYSQL *conn, int user_id, int book_id) {
char query[256];
sprintf(query, "INSERT INTO borrow_records (user_id, book_id, borrow_date) VALUES (%d, %d, CURDATE())", user_id, book_id);
return execute_update(conn, query);
}
// 归还图书
int return_book(MYSQL *conn, int user_id, int book_id) {
char query[256];
sprintf(query, "UPDATE borrow_records SET return_date = CURDATE(), status = 'returned' WHERE user_id = %d AND book_id = %d AND status = 'borrowed'", user_id, book_id);
return execute_update(conn, query);
}
// 获取用户的借阅记录
MYSQL_RES* get_borrow_records(MYSQL *conn, int user_id) {
char query[256];
sprintf(query, "SELECT * FROM borrow_records WHERE user_id = %d", user_id);
return execute_query(conn, query);
}
预定管理模块
include/reservation.h
#ifndef RESERVATION_H
#define RESERVATION_H
#include <mysql/mysql.h>
// 预定图书
int reserve_book(MYSQL *conn, int user_id, int book_id);
// 获取用户的预定记录
MYSQL_RES* get_reservations(MYSQL *conn, int user_id);
#endif
src/reservation.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "reservation.h"
// 预定图书
int reserve_book(MYSQL *conn, int user_id, int book_id) {
char query[256];
sprintf(query, "INSERT INTO reservations (user_id, book_id, reservation_date) VALUES (%d, %d, CURDATE())", user_id, book_id);
return execute_update(conn, query);
}
// 获取用户的预定记录
MYSQL_RES* get_reservations(MYSQL *conn, int user_id) {
char query[256];
sprintf(query, "SELECT * FROM reservations WHERE user_id = %d", user_id);
return execute_query(conn, query);
}
主程序模块
src/main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "user.h"
#include "book.h"
#include "borrow.h"
#include "reservation.h"
void show_menu() {
printf("\n*** 图书馆管理系统 ***\n");
printf("1. 用户注册\n");
printf("2. 用户登录\n");
printf("3. 添加图书 (管理员)\n");
printf("4. 查看所有图书\n");
printf("5. 借阅图书\n");
printf("6. 归还图书\n");
printf("7. 预定图书\n");
printf("8. 查看借阅记录\n");
printf("9. 退出\n");
}
int main() {
MYSQL *conn = init_db();
if (conn == NULL) {
return 1;
}
int choice;
char username[50], password[100], role[10];
int user_id;
while (1) {
show_menu();
printf("请选择操作: ");
scanf("%d", &choice);
switch (choice) {
case 1: // 用户注册
printf("输入用户名: ");
scanf("%s", username);
printf("输入密码: ");
scanf("%s", password);
printf("输入角色 (admin/user): ");
scanf("%s", role);
user_register(conn, username, password, role);
break;
case 2: // 用户登录
printf("输入用户名: ");
scanf("%s", username);
printf("输入密码: ");
scanf("%s", password);
if (user_login(conn, username, password)) {
printf("登录成功!\n");
user_id = mysql_insert_id(conn); // 假设这里是用户 ID
} else {
printf("登录失败!\n");
}
break;
case 3: // 添加图书(仅管理员)
// 此处需要判断用户角色,省略
break;
case 4: // 查看所有图书
{
MYSQL_RES *res = get_all_books(conn);
MYSQL_ROW row;
printf("图书列表:\n");
while ((row = mysql_fetch_row(res))) {
printf("ID: %s, 书名: %s, 作者: %s, 库存: %s\n", row[0], row[1], row[2], row[3]);
}
mysql_free_result(res);
break;
}
case 5: // 借阅图书
printf("输入图书 ID: ");
int book_id;
scanf("%d", &book_id);
borrow_book(conn, user_id, book_id);
break;
case 6: // 归还图书
printf("输入图书 ID: ");
scanf("%d", &book_id);
return_book(conn, user_id, book_id);
break;
case 7: // 预定图书
printf("输入图书 ID: ");
scanf("%d", &book_id);
reserve_book(conn, user_id, book_id);
break;
case 8: // 查看借阅记录
{
MYSQL_RES *res = get_borrow_records(conn, user_id);
MYSQL_ROW row;
printf("借阅记录:\n");
while ((row = mysql_fetch_row(res))) {
printf("ID: %s, 图书 ID: %s, 借阅日期: %s, 归还日期: %s, 状态: %s\n", row[0], row[2], row[3], row[4], row[5]);
}
mysql_free_result(res);
break;
}
case 9: // 退出
close_db(conn);
return 0;
default:
printf("无效选项,请重试。\n");
}
}
return 0;
}