MySQL C++ Connector 默认同步阻塞,无原生异步接口;可行方案包括:1. 用 libmysqlclient 非阻塞 socket 手动轮询;2. 基于 Boost.ASIO 重写协议层;3. 线程池隔离同步调用——最常用且稳健。

MySQL C++ Connector 默认是同步阻塞的
官方 mysqlcppconn(即 MySQL Connector/C++ 8.0)底层调用的是同步 C API,sql::Statement::executeQuery() 和 executeUpdate() 会一直卡住当前线程直到结果返回或超时。Worker 线程一旦执行这类调用,就彻底失去响应能力,无法处理其他任务。
这不是设计缺陷,而是 Connector 本身没提供原生异步接口。想“异步”,必须绕开它直接调度 I/O,或者换更底层的控制方式。
用 libmysqlclient + 非阻塞 socket 手动轮询
这是最可控、也最贴近 MySQL 协议层的做法:跳过 Connector,用 libmysqlclient 的 C API,配合 mysql_real_connect() 的 MYSQL_OPT_NONBLOCK 选项开启非阻塞模式,再结合 select() 或 poll() 监听 socket 可读/可写状态。
-
mysql_init()后必须调用mysql_options(mysql, MYSQL_OPT_NONBLOCK, &on) -
mysql_real_connect()返回NULL不代表失败,要检查mysql_errno()—— 若是CR_SERVER_LOST或CR_CONN_HOST_ERROR才真失败;若为CR_ASYNC_READY(注意:仅限启用了非阻塞后)说明连接已就绪 - 执行查询前先调用
mysql_send_query(),它立即返回;之后用select()等待 socket 可读,再调用mysql_read_query_result()获取结果集 - 整个流程需自己管理状态机:CONNECTING → SENDING → READING_RESULT → FETCHING_ROWS,不能假定一次
select()就到位
用 Boost.ASIO 封装 MySQL 协议(推荐但需重写协议解析)
如果你已在用 Boost.ASIO 做网络调度,可以基于其 async_read()/async_write() 构建纯异步 MySQL 客户端。但这不是“接入一个库就完事”,而是要手写或复用开源协议层(如 mysql_async 或 seastar 中的 MySQL 实现)。
立即学习“C++免费学习笔记(深入)”;
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
关键点:
- MySQL 协议是文本+二进制混合的握手、认证、命令、结果流,包长靠前置 3 字节长度编码,必须严格按字节解析
- 不能依赖
mysqlclient的MYSQL_RES*,得自己把OK_Packet、EOF_Packet、ColumnDefinition、RowData解出来 -
boost::asio::io_context必须和 Worker 线程绑定(通常每个线程一个io_context或用strand保序),否则回调可能跨线程触发导致数据竞争
更现实的选择:用线程池 + 同步调用隔离 I/O
对大多数服务来说,“真异步 MySQL”投入产出比太低。更常见且稳健的做法是:让 Worker 线程只做逻辑编排,把 sql::Statement::executeQuery() 这类调用扔进独立的 I/O 线程池(比如 std::thread + queue<:function>></:function>),用 std::promise/std::future 或 callback 传递结果。
这样做的好处:
- 不用碰 MySQL 协议细节,兼容所有 Connector 版本和连接池方案(如
sql::mysql::MySQLConnectionPool) - 避免因非阻塞 socket 状态机 bug 导致连接泄漏或半截响应
- 线程数可控:I/O 池大小 ≈ 数据库最大连接数,而非并发请求数,资源更可预测
唯一要注意的是,别在 I/O 线程里再调用其他阻塞操作(比如文件读写、另一个 DB 查询),否则池子会堵死。
协议层异步看起来干净,但出问题时 debug 成本远高于线程池——尤其是连接复用、SSL 握手、字符集转换这些隐藏路径,很容易漏掉状态分支。

















