1. 为什么需要C/C++连接MySQL?
在嵌入式系统、高性能服务器、游戏开发等场景中,C/C++程序经常需要直接操作数据库。与PHP/Python等脚本语言不同,C/C++没有内置的数据库连接模块,需要开发者手动处理连接池、SQL语句构造、结果集解析等底层操作。这种直接操作的方式虽然复杂,但能带来显著的性能优势——某金融系统实测显示,用C++直接连接MySQL比通过中间件效率提升40%以上。
2. 环境准备与库选型
2.1 MySQL C Connector安装
官方提供的MySQL C Connector是最稳定的选择。在Ubuntu上安装:
bash复制sudo apt-get install libmysqlclient-dev
Windows用户需从MySQL官网下载对应版本的Connector,注意区分32位/64位。安装后要将libmysql.lib和mysql.h所在目录分别添加到项目的库目录和包含目录。
注意:务必保持Connector版本与MySQL服务器版本兼容。我曾遇到Connector 8.0连接MySQL 5.7导致认证失败的问题,最终降级Connector才解决。
2.2 编译参数配置
以CMake为例,关键配置如下:
cmake复制find_package(MySQL REQUIRED)
include_directories(${MYSQL_INCLUDE_DIR})
target_link_libraries(your_target ${MYSQL_LIBRARY})
如果使用g++直接编译:
bash复制g++ your_code.cpp -lmysqlclient -I/usr/include/mysql -o app
3. 连接数据库实战
3.1 建立连接的核心代码
cpp复制MYSQL *conn = mysql_init(nullptr);
if (!mysql_real_connect(
conn,
"localhost", // 主机
"user", // 用户名
"password", // 密码
"testdb", // 数据库名
3306, // 端口
nullptr, // Unix socket
0 // 客户端标志
)) {
fprintf(stderr, "连接失败: %s\n", mysql_error(conn));
mysql_close(conn);
return -1;
}
关键参数说明:
- 客户端标志可设置为CLIENT_MULTI_STATEMENTS支持多语句执行
- 超时设置通过mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout)实现
- 建议连接后立即设置字符集:mysql_set_character_set(conn, "utf8mb4")
3.2 连接池实现要点
高频访问场景建议使用连接池。一个简易实现方案:
cpp复制class ConnectionPool {
std::queue<MYSQL*> idle_conns;
std::mutex mtx;
public:
MYSQL* get_connection() {
std::lock_guard<std::mutex> lock(mtx);
if(idle_conns.empty()) {
return create_new_connection();
}
auto conn = idle_conns.front();
idle_conns.pop();
if(mysql_ping(conn) != 0) {
mysql_close(conn);
return create_new_connection();
}
return conn;
}
// 其他方法省略...
};
4. SQL执行全流程解析
4.1 基础查询示例
cpp复制const char* query = "SELECT id, name FROM users WHERE age > 20";
if(mysql_query(conn, query)) {
// 错误处理
}
MYSQL_RES* result = mysql_store_result(conn);
if(!result) {
// 处理无结果集或错误情况
}
int num_fields = mysql_num_fields(result);
MYSQL_ROW row;
while((row = mysql_fetch_row(result))) {
unsigned long* lengths = mysql_fetch_lengths(result);
printf("ID: %s, Name: %.*s\n",
row[0],
(int)lengths[1], // 安全处理二进制数据
row[1]);
}
mysql_free_result(result); // 必须释放!
4.2 参数化查询防注入
使用mysql_stmt_init实现预处理:
cpp复制MYSQL_STMT* stmt = mysql_stmt_init(conn);
const char* sql = "INSERT INTO products (name, price) VALUES (?, ?)";
mysql_stmt_prepare(stmt, sql, strlen(sql));
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
char name[100];
double price;
// 绑定参数
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = name;
bind[0].buffer_length = sizeof(name);
bind[0].length = &name_len;
bind[1].buffer_type = MYSQL_TYPE_DOUBLE;
bind[1].buffer = &price;
// 执行预处理语句...
4.3 事务处理示例
cpp复制mysql_autocommit(conn, 0); // 关闭自动提交
try {
mysql_query(conn, "UPDATE accounts SET balance=balance-100 WHERE user='A'");
mysql_query(conn, "UPDATE accounts SET balance=balance+100 WHERE user='B'");
mysql_commit(conn);
} catch(...) {
mysql_rollback(conn);
throw;
} finally {
mysql_autocommit(conn, 1);
}
5. 高级特性与性能优化
5.1 批量插入技巧
使用LOAD DATA INFILE比单条INSERT快10-100倍:
cpp复制// 先导出数据到CSV
FILE* tmp = tmpfile();
// 写入数据...
fflush(tmp);
rewind(tmp);
char sql[256];
sprintf(sql, "LOAD DATA LOCAL INFILE '%d' INTO TABLE big_data", fileno(tmp));
mysql_query(conn, sql);
5.2 多结果集处理
当执行包含多个SELECT的存储过程时:
cpp复制do {
MYSQL_RES* result = mysql_store_result(conn);
// 处理结果集...
mysql_free_result(result);
} while(!mysql_next_result(conn));
5.3 连接状态监控
定期检查连接健康状态:
cpp复制if(mysql_ping(conn) != 0) {
// 重连逻辑
mysql_close(conn);
conn = mysql_init(nullptr);
// 重新连接...
}
6. 常见问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 连接时报认证错误 | 密码错误/加密方式不匹配 | 使用mysql_native_password插件 |
| 查询返回乱码 | 字符集设置不一致 | 连接后执行SET NAMES utf8mb4 |
| 预处理语句执行慢 | 参数绑定类型不匹配 | 检查MYSQL_BIND的buffer_type |
| 频繁断连 | 服务器wait_timeout设置过短 | 增加超时或定期发送ping |
内存泄漏检查要点:
- 每个mysql_store_result()必须对应mysql_free_result()
- 每个mysql_stmt_init()必须对应mysql_stmt_close()
- 使用valgrind检查未释放的连接
7. 现代C++的封装实践
推荐使用RAII风格封装:
cpp复制class MySQLConnection {
MYSQL* conn;
public:
MySQLConnection() {
conn = mysql_init(nullptr);
// 连接参数...
}
~MySQLConnection() {
if(conn) mysql_close(conn);
}
// 禁用拷贝
MySQLConnection(const MySQLConnection&) = delete;
MySQLConnection& operator=(const MySQLConnection&) = delete;
// 移动语义支持
MySQLConnection(MySQLConnection&& other) noexcept {
conn = other.conn;
other.conn = nullptr;
}
// 其他方法...
};
结合C++17的optional处理查询结果:
cpp复制std::optional<std::vector<User>> query_users(MYSQL* conn) {
if(mysql_query(conn, "SELECT...")) {
return std::nullopt;
}
// 解析结果...
}
实际项目中,可以考虑使用ORM框架如ODB或mysql++,但了解底层API仍是处理复杂场景的基础。我在处理一个高频交易系统时,就因为直接使用C API而不是ORM,成功将查询延迟从15ms降到了3ms以下。