[C语言实战]C语言操作MySQL数据库(八)
[C语言实战]C语言操作MySQL数据库(八)
MySQL作为最流行的开源关系型数据库,与C语言的结合在系统编程、嵌入式开发等领域应用广泛。本文将全面讲解如何使用C语言连接MySQL数据库并实现增删改查(CRUD)操作,包含详细代码示例和最佳实践。
一、环境配置与基础准备
1.1 开发环境搭建
Linux系统安装MySQL开发库(方案一):
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install libmysqlclient-dev# CentOS/RHEL
sudo yum install mysql-devel
Linux系统安装MySQL开发库(方案二):
- 下载MySQL Connector/C
2.安装
dpkg-deb -x libmysqlclient-dev_9.2.0-1ubuntu24.04_amd64.deb .
3.将mysql的头文件和库文件放在系统环境下(可选)
#进入安装目录
cd usr
cp -r mysql/ /usr/include/
#进入安装后的lib目录下
cd /usr/lib/x86_64-linux-gnu
cp * -r /usr/lib/x86_64-linux-gnu/
1.2 项目基础配置
必要头文件:
#include <mysql/mysql.h> // MySQL主头文件
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数
编译命令:
#根据实际情况修改头文件和库位置:-I/home/c/mysqldemo/usr/include/mysql(可选)
gcc mysql_demo.c -o mysql_demo -lmysqlclient -I/home/c/mysqldemo/usr/include/mysql -L/home/c/mysqldemo/usr/lib/x86_64-linux-gnu -lmysqlclient #假如mysql8版本是docker启动且是按方案二安装,则将docker中的mysql_native_password.so复制在宿主机(可选)
docker cp 94:/usr/lib/mysql/plugin/mysql_native_password.so 94:/usr/lib/mysql/plugin/#实际编译Makefile,直接make# 编译器和标志
CC = gcc
CFLAGS = -Wall -g -std=c99
CPPFLAGS = -I/home/c/mysqldemo/usr/include/mysql
LDFLAGS = -L/home/c/mysqldemo/usr/lib/x86_64-linux-gnu
LDLIBS = -lmysqlclient -lssl -lcrypto -lpthread -lstdc++ -lz# 目标和源文件
TARGET = mysql_demo
SRCS = mysql_demo.c
OBJS = $(SRCS:.c=.o)# 默认目标
all: $(TARGET)# 链接目标文件
$(TARGET): $(OBJS)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)# 编译源文件
%.o: %.c$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@# 清理生成文件
clean:rm -f $(OBJS) $(TARGET)
编译为可执行文件:
测试:
二、数据库连接管理
2.1 建立数据库连接
//结构体定义
typedef struct {MYSQL *conn;const char *host;const char *user;const char *passwd;const char *dbname;unsigned int port;
} Database;int db_connect(Database *db) {db->conn = mysql_init(NULL);if (!db->conn) {handle_mysql_error(db->conn, "数据库初始化失败");return -1;}// 设置连接超时和认证插件(解决5.7/8.0兼容问题)unsigned int timeout = 5;mysql_options(db->conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);mysql_options(db->conn, MYSQL_DEFAULT_AUTH, "mysql_native_password");if (!mysql_real_connect(db->conn, db->host, db->user, db->passwd,db->dbname, db->port, NULL, CLIENT_MULTI_STATEMENTS)) {handle_mysql_error(db->conn, "数据库连接失败");mysql_close(db->conn);return -1;}// 强制设置字符集并检查错误if (mysql_set_character_set(db->conn, "utf8mb4")) {handle_mysql_error(db->conn, "字符集设置失败");mysql_close(db->conn);return -1;}return 0;
}
2.2 连接参数详解
参数名 | 类型 | 说明 |
---|---|---|
host | const char* | 主机地址,本地为localhost |
user | const char* | 数据库用户名 |
passwd | const char* | 数据库密码 |
dbname | const char* | 默认数据库名 |
port | unsigned int | 端口号 |
三、CRUD操作实战
3.1 创建数据表
// 创建表const char *create_table_sql = "CREATE TABLE IF NOT EXISTS products (""id INT AUTO_INCREMENT PRIMARY KEY,""name VARCHAR(100) NOT NULL,""price DECIMAL(10,2) NOT NULL,""stock INT DEFAULT 0"") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";if (mysql_query(db.conn, create_table_sql)) {handle_mysql_error(db.conn, "表创建失败");db_disconnect(&db);return 1;}
3.2 安全插入数据(防SQL注入)
int insert_user(MYSQL *conn, const char *username, const char *password, const char *email) {MYSQL_STMT *stmt;MYSQL_BIND bind[3];stmt = mysql_stmt_init(conn);if (!stmt) {fprintf(stderr, "初始化STMT失败\n");return -1;}const char *query = "INSERT INTO users (username, password, email) ""VALUES (?, ?, ?)";if (mysql_stmt_prepare(stmt, query, strlen(query))) {fprintf(stderr, "准备失败: %s\n", mysql_stmt_error(stmt));mysql_stmt_close(stmt);return -1;}// 初始化绑定结构memset(bind, 0, sizeof(bind));// 绑定用户名bind[0].buffer_type = MYSQL_TYPE_STRING;bind[0].buffer = (char *)username;bind[0].buffer_length = strlen(username);bind[0].is_null = 0;// 绑定密码bind[1].buffer_type = MYSQL_TYPE_STRING;bind[1].buffer = (char *)password;bind[1].buffer_length = strlen(password);bind[1].is_null = 0;// 绑定邮箱bind[2].buffer_type = MYSQL_TYPE_STRING;bind[2].buffer = (char *)email;bind[2].buffer_length = strlen(email);bind[2].is_null = (email == NULL) ? 1 : 0;if (mysql_stmt_bind_param(stmt, bind)) {fprintf(stderr, "绑定参数失败: %s\n", mysql_stmt_error(stmt));mysql_stmt_close(stmt);return -1;}if (mysql_stmt_execute(stmt)) {fprintf(stderr, "执行失败: %s\n", mysql_stmt_error(stmt));mysql_stmt_close(stmt);return -1;}printf("插入成功,ID: %llu\n", mysql_stmt_insert_id(stmt));mysql_stmt_close(stmt);return 0;
}
3.3 查询数据与结果处理
typedef struct {int id;char username[50];char email[100];char created_at[20];
} User;int get_users(MYSQL *conn, User **users, int *count) {if (mysql_query(conn, "SELECT id, username, email, created_at FROM users")) {fprintf(stderr, "查询失败: %s\n", mysql_error(conn));return -1;}MYSQL_RES *result = mysql_store_result(conn);if (!result) {fprintf(stderr, "获取结果失败: %s\n", mysql_error(conn));return -1;}int num_fields = mysql_num_fields(result);*count = mysql_num_rows(result);if (*count == 0) {mysql_free_result(result);return 0;}*users = (User *)malloc(sizeof(User) * (*count));if (!*users) {fprintf(stderr, "内存分配失败\n");mysql_free_result(result);return -1;}MYSQL_ROW row;int i = 0;while ((row = mysql_fetch_row(result))) {(*users)[i].id = atoi(row[0]);strncpy((*users)[i].username, row[1], 49);if (row[2]) strncpy((*users)[i].email, row[2], 99);if (row[3]) strncpy((*users)[i].created_at, row[3], 19);i++;}mysql_free_result(result);return 0;
}
3.4 更新与删除操作
// 更新用户邮箱
int update_user_email(MYSQL *conn, int user_id, const char *new_email) {MYSQL_STMT *stmt = mysql_stmt_init(conn);if (!stmt) return -1;const char *query = "UPDATE users SET email = ? WHERE id = ?";if (mysql_stmt_prepare(stmt, query, strlen(query))) {mysql_stmt_close(stmt);return -1;}MYSQL_BIND bind[2];memset(bind, 0, sizeof(bind));// 绑定新邮箱bind[0].buffer_type = MYSQL_TYPE_STRING;bind[0].buffer = (char *)new_email;bind[0].buffer_length = strlen(new_email);// 绑定用户IDbind[1].buffer_type = MYSQL_TYPE_LONG;bind[1].buffer = &user_id;if (mysql_stmt_bind_param(stmt, bind) || mysql_stmt_execute(stmt)) {mysql_stmt_close(stmt);return -1;}int affected = mysql_stmt_affected_rows(stmt);mysql_stmt_close(stmt);if (affected == 0) {printf("未找到用户或数据未改变\n");return 0;}printf("成功更新%d条记录\n", affected);return 1;
}// 删除用户
int delete_user(MYSQL *conn, int user_id) {char query[100];snprintf(query, sizeof(query), "DELETE FROM users WHERE id = %d", user_id);if (mysql_query(conn, query)) {fprintf(stderr, "删除失败: %s\n", mysql_error(conn));return -1;}int affected = mysql_affected_rows(conn);if (affected == 0) {printf("未找到指定用户\n");return 0;}printf("成功删除%d条记录\n", affected);return 1;
}
四、高级特性与最佳实践
4.1 事务处理
int transfer_money(MYSQL *conn, int from_id, int to_id, double amount) {// 开始事务if (mysql_query(conn, "START TRANSACTION")) {return -1;}// 执行转账操作char query[256];snprintf(query, sizeof(query), "UPDATE accounts SET balance = balance - %.2f WHERE user_id = %d",amount, from_id);if (mysql_query(conn, query)) {mysql_query(conn, "ROLLBACK");return -1;}snprintf(query, sizeof(query), "UPDATE accounts SET balance = balance + %.2f WHERE user_id = %d",amount, to_id);if (mysql_query(conn, query)) {mysql_query(conn, "ROLLBACK");return -1;}// 提交事务if (mysql_query(conn, "COMMIT")) {mysql_query(conn, "ROLLBACK");return -1;}return 0;
}
4.2 连接池实现
#define MAX_CONNECTIONS 10typedef struct {MYSQL *conn;int in_use;time_t last_used;
} DBConnection;DBConnection connection_pool[MAX_CONNECTIONS];
pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;MYSQL *get_connection() {pthread_mutex_lock(&pool_mutex);MYSQL *conn = NULL;int oldest_index = -1;time_t oldest_time = time(NULL);for (int i = 0; i < MAX_CONNECTIONS; i++) {if (!connection_pool[i].in_use) {if (connection_pool[i].conn) {// 复用现有连接connection_pool[i].in_use = 1;connection_pool[i].last_used = time(NULL);conn = connection_pool[i].conn;break;} else {// 创建新连接MYSQL *new_conn = mysql_init(NULL);if (mysql_real_connect(new_conn, "localhost", "user", "pass", "db", 0, NULL, 0)) {connection_pool[i].conn = new_conn;connection_pool[i].in_use = 1;connection_pool[i].last_used = time(NULL);conn = new_conn;break;}}} else if (connection_pool[i].last_used < oldest_time) {oldest_index = i;oldest_time = connection_pool[i].last_used;}}pthread_mutex_unlock(&pool_mutex);return conn;
}void release_connection(MYSQL *conn) {pthread_mutex_lock(&pool_mutex);for (int i = 0; i < MAX_CONNECTIONS; i++) {if (connection_pool[i].conn == conn) {connection_pool[i].in_use = 0;break;}}pthread_mutex_unlock(&pool_mutex);
}
4.3 性能优化技巧
- 批量插入优化:
// 使用多值插入语法
mysql_query(conn, "INSERT INTO users (username, email) VALUES ('user1', 'u1@ex.com'), ('user2', 'u2@ex.com')");
- 查询缓存:
// 设置查询缓存
mysql_query(conn, "SET SESSION query_cache_type = ON");
- 索引优化:
// 添加合适索引
mysql_query(conn, "ALTER TABLE users ADD INDEX idx_username (username)");
五、错误处理与调试
5.1 全面的错误处理机制
void handle_mysql_error(MYSQL *conn) {if (mysql_errno(conn)) {fprintf(stderr, "MySQL错误 %d: %s\n", mysql_errno(conn), mysql_error(conn));switch(mysql_errno(conn)) {case CR_CONNECTION_ERROR:// 处理连接错误break;case CR_SERVER_GONE_ERROR:// 处理连接断开break;case ER_DUP_ENTRY:// 处理唯一键冲突break;default:// 其他错误break;}}
}
5.2 日志记录
void log_mysql_operation(const char *operation, const char *query, int affected_rows, time_t duration) {FILE *log_file = fopen("mysql.log", "a");if (log_file) {fprintf(log_file, "[%ld] %s: %s\n\t影响行数: %d, 耗时: %ldms\n",time(NULL), operation, query, affected_rows, duration);fclose(log_file);}
}
六、安全防护措施
6.1 SQL注入防护
- 永远使用预处理语句
- 输入验证:
int is_valid_input(const char *input) {const char *danger_chars = "'\"\\;";return strpbrk(input, danger_chars) == NULL;
}
- 最小权限原则:
// 使用只具有必要权限的数据库账号
mysql_real_connect(conn, "localhost", "app_user", "limited_pass", "app_db", 0, NULL, 0);
6.2 敏感数据保护
// 密码加密存储
void store_password(MYSQL *conn, const char *username, const char *password) {char hashed_pwd[61]; // bcrypt哈希长度bcrypt_hashpw(password, bcrypt_gensalt(12), hashed_pwd);// 使用预处理语句存储MYSQL_STMT *stmt = mysql_stmt_init(conn);const char *query = "UPDATE users SET password = ? WHERE username = ?";// ...绑定参数并执行
}
七、完整示例
//mysql_demo.c
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct {MYSQL *conn;const char *host;const char *user;const char *passwd;const char *dbname;unsigned int port;
} Database;void handle_mysql_error(MYSQL *conn, const char *msg) {if (mysql_errno(conn)) {fprintf(stderr, "%s: MySQL错误 %d: %s\n", msg, mysql_errno(conn), mysql_error(conn));}
}int db_connect(Database *db) {db->conn = mysql_init(NULL);if (!db->conn) {handle_mysql_error(db->conn, "数据库初始化失败");return -1;}// 设置连接超时和认证插件(解决5.7/8.0兼容问题)unsigned int timeout = 5;mysql_options(db->conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);mysql_options(db->conn, MYSQL_DEFAULT_AUTH, "mysql_native_password");if (!mysql_real_connect(db->conn, db->host, db->user, db->passwd,db->dbname, db->port, NULL, CLIENT_MULTI_STATEMENTS)) {handle_mysql_error(db->conn, "数据库连接失败");mysql_close(db->conn);return -1;}// 强制设置字符集并检查错误if (mysql_set_character_set(db->conn, "utf8mb4")) {handle_mysql_error(db->conn, "字符集设置失败");mysql_close(db->conn);return -1;}return 0;
}void db_disconnect(Database *db) {if (db->conn) {mysql_close(db->conn);db->conn = NULL;}
}void print_result_set(MYSQL_RES *result) {if (!result) return;int num_fields = mysql_num_fields(result);MYSQL_FIELD *fields = mysql_fetch_fields(result);MYSQL_ROW row;// 打印表头for (int i = 0; i < num_fields; i++) {printf("%-15s", fields[i].name);}printf("\n");// 打印分隔线for (int i = 0; i < num_fields; i++) {printf("---------------");}printf("\n");// 打印数据行(直接使用行数据,避免内存分配)while ((row = mysql_fetch_row(result))) {for (int i = 0; i < num_fields; i++) {printf("%-15s", row[i] ? row[i] : "NULL");}printf("\n");}
}int main() {Database db = {.host = "127.0.0.1",.user = "root",.passwd = "123456",.dbname = "test_db",.port = 3306};printf("正在连接数据库 %s:%d...\n", db.host, db.port);if (db_connect(&db)) return 1;printf("数据库连接成功\n\n");// 创建表const char *create_table_sql = "CREATE TABLE IF NOT EXISTS products (""id INT AUTO_INCREMENT PRIMARY KEY,""name VARCHAR(100) NOT NULL,""price DECIMAL(10,2) NOT NULL,""stock INT DEFAULT 0"") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";if (mysql_query(db.conn, create_table_sql)) {handle_mysql_error(db.conn, "表创建失败");db_disconnect(&db);return 1;}printf("表创建成功\n\n");// 插入数据(使用预处理语句避免SQL注入,这里简化演示)const char *insert_sql = "INSERT INTO products (name, price, stock) VALUES ""('笔记本电脑', 9999.99, 10),""('智能手机', 5999.99, 20),""('平板电脑', 3999.99, 15)";if (mysql_query(db.conn, insert_sql)) {handle_mysql_error(db.conn, "数据插入失败");db_disconnect(&db);return 1;}printf("数据插入成功,影响行数: %lu\n\n", mysql_affected_rows(db.conn));// 查询数据if (mysql_query(db.conn, "SELECT * FROM products")) {handle_mysql_error(db.conn, "数据查询失败");db_disconnect(&db);return 1;}printf("数据查询成功\n");print_result_set(mysql_store_result(db.conn));// 安全释放资源db_disconnect(&db);printf("\n数据库连接已关闭\n");return 0;
}
希望本教程对您有帮助,请点赞❤️收藏⭐关注支持!欢迎在评论区留言交流技术细节!