1. 环境准备与基础配置
在开始使用C++原生API操作MySQL之前,我们需要先搭建开发环境。这里以Windows平台为例(Linux/macOS配置原理类似),假设你已经安装好了MySQL Server和Visual Studio开发环境。
关键步骤:
- 找到MySQL安装目录下的
include和lib文件夹(通常位于C:\Program Files\MySQL\MySQL Server 8.0) - 在VS项目中配置附加包含目录:右键项目 → 属性 → C/C++ → 常规 → 附加包含目录
- 配置库目录:链接器 → 常规 → 附加库目录
- 添加依赖项:链接器 → 输入 → 附加依赖项中添加
libmysql.lib
注意:运行时需要将
libmysql.dll放在可执行文件同级目录或系统PATH路径下。我第一次配置时曾因为漏掉这一步,程序运行时直接崩溃,调试了半天才发现问题。
2. 核心API封装设计
2.1 连接管理类封装
我们先设计一个基础的MySQL连接类,采用RAII机制管理资源:
class MySQLConnection { public: MySQLConnection(const std::string& host, const std::string& user, const std::string& password, const std::string& db, unsigned int port = 3306) : m_conn(nullptr) { mysql_init(&m_mysql); m_conn = mysql_real_connect(&m_mysql, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0); if (!m_conn) { throw std::runtime_error(mysql_error(&m_mysql)); } } ~MySQLConnection() { if (m_conn) { mysql_close(&m_mysql); } } // 禁用拷贝构造和赋值 MySQLConnection(const MySQLConnection&) = delete; MySQLConnection& operator=(const MySQLConnection&) = delete; private: MYSQL m_mysql; MYSQL* m_conn; };这个基础版本已经实现了:
- 自动初始化连接
- 异常安全的资源释放
- 防止意外的对象拷贝
2.2 查询执行与结果处理
接下来我们扩展查询功能,添加一个执行SQL的方法:
class MySQLQuery { public: explicit MySQLQuery(MySQLConnection& conn) : m_conn(conn), m_res(nullptr) {} bool execute(const std::string& sql) { if (mysql_real_query(m_conn.get(), sql.c_str(), sql.length()) != 0) { m_error = mysql_error(m_conn.get()); return false; } m_res = mysql_store_result(m_conn.get()); return true; } std::vector<std::vector<std::string>> fetchAll() { std::vector<std::vector<std::string>> results; if (!m_res) return results; int num_fields = mysql_num_fields(m_res); MYSQL_ROW row; while ((row = mysql_fetch_row(m_res))) { std::vector<std::string> record; for (int i = 0; i < num_fields; ++i) { record.emplace_back(row[i] ? row[i] : "NULL"); } results.emplace_back(std::move(record)); } return results; } ~MySQLQuery() { if (m_res) { mysql_free_result(m_res); } } private: MySQLConnection& m_conn; MYSQL_RES* m_res; std::string m_error; };3. 高级功能实现
3.1 参数化查询防注入
直接拼接SQL语句存在SQL注入风险。我们可以实现一个简单的参数化查询:
class MySQLStatement { public: explicit MySQLStatement(MySQLConnection& conn) : m_conn(conn), m_stmt(nullptr) { m_stmt = mysql_stmt_init(m_conn.get()); if (!m_stmt) { throw std::runtime_error(mysql_error(m_conn.get())); } } bool prepare(const std::string& sql) { if (mysql_stmt_prepare(m_stmt, sql.c_str(), sql.length()) != 0) { m_error = mysql_stmt_error(m_stmt); return false; } return true; } bool execute() { if (mysql_stmt_execute(m_stmt) != 0) { m_error = mysql_stmt_error(m_stmt); return false; } return true; } ~MySQLStatement() { if (m_stmt) { mysql_stmt_close(m_stmt); } } private: MySQLConnection& m_conn; MYSQL_STMT* m_stmt; std::string m_error; };使用示例:
MySQLConnection conn("localhost", "user", "password", "test_db"); MySQLStatement stmt(conn); stmt.prepare("INSERT INTO users (name, age) VALUES (?, ?)"); // 实际使用时需要绑定参数 // MYSQL_BIND bind[2]; // ...设置bind参数... // mysql_stmt_bind_param(m_stmt, bind);3.2 连接池实现
对于高并发场景,频繁创建销毁连接会影响性能。下面是一个简单的连接池实现思路:
class MySQLConnectionPool { public: MySQLConnectionPool(size_t pool_size, const std::string& host, ...) : m_host(host), ... { for (size_t i = 0; i < pool_size; ++i) { m_pool.emplace(std::make_shared<MySQLConnection>(host, ...)); } } std::shared_ptr<MySQLConnection> getConnection() { std::unique_lock<std::mutex> lock(m_mutex); m_cond.wait(lock, [this] { return !m_pool.empty(); }); auto conn = m_pool.front(); m_pool.pop(); return conn; } void returnConnection(std::shared_ptr<MySQLConnection> conn) { std::unique_lock<std::mutex> lock(m_mutex); m_pool.push(conn); m_cond.notify_one(); } private: std::queue<std::shared_ptr<MySQLConnection>> m_pool; std::mutex m_mutex; std::condition_variable m_cond; std::string m_host; // 其他连接参数... };4. 错误处理与调试技巧
4.1 完善的错误处理机制
MySQL API的错误处理有几个关键点需要注意:
- 每次API调用后都应检查返回值
- 错误信息需要通过
mysql_error()或mysql_stmt_error()获取 - 错误码可以通过
mysql_errno()获取
建议封装一个统一的错误处理类:
class MySQLError { public: static void check(MYSQL* conn, int ret) { if (ret != 0) { throw std::runtime_error( std::to_string(mysql_errno(conn)) + ": " + mysql_error(conn)); } } static void check_stmt(MYSQL_STMT* stmt, int ret) { if (ret != 0) { throw std::runtime_error( std::to_string(mysql_stmt_errno(stmt)) + ": " + mysql_stmt_error(stmt)); } } };4.2 常见问题排查
- 连接失败:检查MySQL服务是否运行、用户名密码是否正确、是否有远程访问权限
- 查询返回乱码:确保连接后设置了正确的字符集
mysql_set_character_set(&mysql, "utf8mb4") - 内存泄漏:确保每个
mysql_store_result()都有对应的mysql_free_result() - 连接超时:MySQL默认有8小时闲置断开机制,可以在连接字符串中添加
CLIENT_RECONNECT标志
5. 性能优化建议
- 批量操作:对于大量插入,使用
INSERT INTO ... VALUES (...), (...), ...比多次单条插入快10倍以上 - 预处理语句复用:创建一次预处理语句,多次执行比每次都准备新语句高效
- 合理使用事务:将多个写操作放在一个事务中,减少磁盘I/O
- 结果集处理:对于大结果集,使用
mysql_use_result()替代mysql_store_result()减少内存占用 - 连接池大小:一般设置为CPU核心数的2-3倍
我曾经在一个日志处理项目中,通过将单条插入改为批量插入+事务,使性能从每秒200条提升到15000条。