一、前置条件:环境准备
C++操作MySQL依赖MySQL官方的C API客户端库(libmysqlclient),需先完成安装和配置:
1. Linux系统(Ubuntu/Debian)
bash
# 安装客户端开发库(包含头文件和链接库)
sudo apt-get update
sudo apt-get install libmysqlclient-dev
# 验证安装:查看头文件和库文件
ls /usr/include/mysql/ # 头文件目录
ls /usr/lib/x86_64-linux-gnu/libmysqlclient.so # 库文件
2. Windows系统
- 下载MySQL C API:从MySQL官网下载"MySQL Connector/C";
- 配置项目:
- 头文件路径:添加
mysql/include到项目的"附加包含目录"; - 库文件路径:添加
mysql/lib到项目的"附加库目录"; - 链接库:添加
libmysql.lib(编译时)和libmysql.dll(运行时,放到可执行文件同目录)。
- 头文件路径:添加
3. 编译命令(Linux)
编写代码后,编译时需链接MySQL库:
bash
g++ mysql_crud.cpp -o mysql_crud -lmysqlclient -std=c++11
二、C++操作MySQL的核心流程
所有数据库操作的基础流程是:
初始化MySQL库 → 创建连接句柄 → 连接数据库 → 执行SQL(增删改查) → 处理结果(查询特有) → 释放资源
三、核心API讲解
先熟悉MySQL C API的关键函数(C++中直接调用这些C风格API):
| 函数名 | 作用 |
|---|---|
mysql_library_init() |
初始化MySQL库(程序启动时调用) |
mysql_init() |
创建并初始化MySQL连接句柄(MYSQL*类型) |
mysql_real_connect() |
建立与MySQL服务器的实际连接(指定主机、用户名、密码、数据库等) |
mysql_query() |
执行SQL语句(增/删/改/查,查询需配合结果集函数) |
mysql_store_result() |
获取查询结果集(将结果加载到内存,适合小数据量) |
mysql_fetch_row() |
逐行读取结果集中的数据 |
mysql_num_fields() |
获取结果集的列数 |
mysql_fetch_field() |
获取列的元数据(列名、类型等) |
mysql_affected_rows() |
获取增/删/改操作影响的行数 |
mysql_free_result() |
释放结果集内存(避免内存泄漏) |
mysql_close() |
关闭数据库连接 |
mysql_library_end() |
释放MySQL库资源(程序结束时调用) |
四、完整CRUD代码示例
下面是包含"连接数据库+增删改查"的完整可运行代码,代码中有详细注释:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
// 引入MySQL C API头文件
#include <mysql/mysql.h>
using namespace std;
// 数据库配置(根据自己的环境修改)
const char* HOST = "localhost"; // 数据库主机(本地为localhost)
const char* USER = "root"; // 数据库用户名
const char* PASSWORD = "your_password"; // 数据库密码
const char* DB_NAME = "test_db"; // 要操作的数据库名
const unsigned int PORT = 3306; // MySQL端口(默认3306)
const char* CHARSET = "utf8mb4"; // 字符集(支持中文)
// 封装MySQL连接类(简化操作,更符合C++风格)
class MySQLDB {
private:
MYSQL* conn; // MySQL连接句柄
public:
// 构造函数:初始化连接句柄
MySQLDB() {
// 初始化MySQL库(0表示初始化所有核心组件)
if (mysql_library_init(0, nullptr, nullptr) != 0) {
cerr << "MySQL库初始化失败!" << endl;
exit(1);
}
// 创建连接句柄
conn = mysql_init(nullptr);
if (conn == nullptr) {
cerr << "创建连接句柄失败:" << mysql_error(conn) << endl;
exit(1);
}
}
// 连接数据库
bool connect() {
// 建立实际连接
if (mysql_real_connect(conn, HOST, USER, PASSWORD, DB_NAME, PORT, nullptr, 0) == nullptr) {
cerr << "数据库连接失败:" << mysql_error(conn) << endl;
return false;
}
// 设置字符集(避免中文乱码)
mysql_set_character_set(conn, CHARSET);
cout << "数据库连接成功!" << endl;
return true;
}
// 1. 新增操作(INSERT)
bool insert(const string& sql) {
if (mysql_query(conn, sql.c_str()) != 0) {
cerr << "插入失败:" << mysql_error(conn) << endl;
return false;
}
// 获取影响的行数(插入成功至少影响1行)
int affected = mysql_affected_rows(conn);
cout << "插入成功,影响行数:" << affected << endl;
return true;
}
// 2. 删除操作(DELETE)
bool remove(const string& sql) {
if (mysql_query(conn, sql.c_str()) != 0) {
cerr << "删除失败:" << mysql_error(conn) << endl;
return false;
}
int affected = mysql_affected_rows(conn);
cout << "删除成功,影响行数:" << affected << endl;
return true;
}
// 3. 修改操作(UPDATE)
bool update(const string& sql) {
if (mysql_query(conn, sql.c_str()) != 0) {
cerr << "修改失败:" << mysql_error(conn) << endl;
return false;
}
int affected = mysql_affected_rows(conn);
cout << "修改成功,影响行数:" << affected << endl;
return true;
}
// 4. 查询操作(SELECT)
bool query(const string& sql) {
if (mysql_query(conn, sql.c_str()) != 0) {
cerr << "查询失败:" << mysql_error(conn) << endl;
return false;
}
// 获取结果集(加载所有数据到内存)
MYSQL_RES* res = mysql_store_result(conn);
if (res == nullptr) {
cerr << "获取结果集失败:" << mysql_error(conn) << endl;
return false;
}
// 获取列数和列名
int col_num = mysql_num_fields(res);
MYSQL_FIELD* fields = mysql_fetch_fields(res);
// 打印列名
for (int i = 0; i < col_num; ++i) {
cout << fields[i].name << "\t";
}
cout << endl;
// 逐行读取数据
MYSQL_ROW row;
while ((row = mysql_fetch_row(res)) != nullptr) {
for (int i = 0; i < col_num; ++i) {
// 注意:NULL值需要特殊处理(row[i]为nullptr)
cout << (row[i] ? row[i] : "NULL") << "\t";
}
cout << endl;
}
// 释放结果集(必须!否则内存泄漏)
mysql_free_result(res);
return true;
}
// 析构函数:释放资源
~MySQLDB() {
if (conn != nullptr) {
mysql_close(conn); // 关闭连接
}
mysql_library_end(); // 释放MySQL库资源
cout << "数据库连接已关闭!" << endl;
}
};
// 测试主函数
int main() {
// 1. 创建数据库对象并连接
MySQLDB db;
if (!db.connect()) {
return 1;
}
// 先创建测试表(如果不存在)
string create_table_sql = "CREATE TABLE IF NOT EXISTS student ("
"id INT PRIMARY KEY AUTO_INCREMENT,"
"name VARCHAR(50) NOT NULL,"
"age INT NOT NULL,"
"score FLOAT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
if (mysql_query(db.conn, create_table_sql.c_str()) != 0) {
cerr << "创建表失败:" << mysql_error(db.conn) << endl;
return 1;
}
// 2. 新增数据
string insert_sql = "INSERT INTO student (name, age, score) VALUES ('张三', 18, 95.5), ('李四', 19, 88.0);";
db.insert(insert_sql);
// 3. 修改数据(把李四的分数改为90.0)
string update_sql = "UPDATE student SET score = 90.0 WHERE name = '李四';";
db.update(update_sql);
// 4. 查询数据
string query_sql = "SELECT * FROM student;";
db.query(query_sql);
// 5. 删除数据(删除年龄为18的记录)
string delete_sql = "DELETE FROM student WHERE age = 18;";
db.remove(delete_sql);
// 再次查询,验证删除结果
cout << "\n删除后的数据:" << endl;
db.query(query_sql);
return 0;
}
五、关键细节解释
1. 连接参数说明
mysql_real_connect的参数:(连接句柄, 主机, 用户名, 密码, 数据库名, 端口, unix套接字, 客户端标志);- 本地连接时
unix套接字传nullptr,客户端标志传0即可。
2. 中文乱码问题
- 必须调用
mysql_set_character_set(conn, "utf8mb4")设置字符集; - 数据库/表的字符集也需设置为
utf8mb4(兼容所有Unicode字符,包括emoji)。
3. 查询结果处理
mysql_store_result():将查询结果全部加载到内存,适合小数据量;大数据量建议用mysql_use_result()(逐行读取,不占内存);mysql_fetch_row()返回char**,每行数据按列存储,NULL值对应row[i] = nullptr,需特殊处理。
4. 错误处理
- 每个MySQL API调用后都要检查返回值(比如
mysql_query返回0表示成功,非0失败); mysql_error(conn):返回最近一次操作的错误信息,调试必备。
5. 防SQL注入(重要!)
上面的示例直接拼接SQL字符串,存在SQL注入风险 (比如输入的name包含' OR 1=1 --)。生产环境需用预处理语句(Prepared Statement):
cpp
// 预处理语句示例(插入数据)
MYSQL_STMT* stmt = mysql_stmt_init(conn);
string sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?);";
mysql_stmt_prepare(stmt, sql.c_str(), sql.length());
// 绑定参数
MYSQL_BIND params[3];
memset(params, 0, sizeof(params));
// 绑定name(字符串)
char name[] = "王五";
params[0].buffer_type = MYSQL_TYPE_STRING;
params[0].buffer = name;
params[0].buffer_length = strlen(name);
// 绑定age(int)
int age = 20;
params[1].buffer_type = MYSQL_TYPE_LONG;
params[1].buffer = &age;
// 绑定score(float)
float score = 92.5;
params[2].buffer_type = MYSQL_TYPE_FLOAT;
params[2].buffer = &score;
mysql_stmt_bind_param(stmt, params);
mysql_stmt_execute(stmt);
mysql_stmt_close(stmt);
六、总结
- C++操作MySQL依赖MySQL C API ,核心是通过
MYSQL句柄管理连接,mysql_query执行SQL; - 增删改操作直接执行SQL,通过
mysql_affected_rows获取影响行数;查询操作需通过mysql_store_result获取结果集并逐行解析; - 关键注意事项:
- 必须初始化/释放MySQL库资源,避免内存泄漏;
- 设置字符集防止中文乱码;
- 生产环境用预处理语句防SQL注入;
- 每个API调用后检查返回值,通过
mysql_error排查错误。