mysql连接句柄非线程安全,必须每线程独占mysql*;postgresql虽支持多线程但连接仍需单线程复用;连接池大小应依据服务端max_connections和实际负载调整,而非简单设为cpu核数2倍。

为什么不能直接用 std::thread + mysql_real_connect 手搓连接池
因为 MySQL C API 的连接句柄(MYSQ L*)不是线程安全的——同一连接被多个线程并发调用 mysql_query 或 mysql_store_result 会触发未定义行为,轻则返回乱数据,重则进程崩溃。PostgreSQL 的 PQconnectdb 虽然支持多线程,但连接本身仍不允许多线程复用(官方文档明确要求“one connection per thread”)。所以“共享连接 + 多线程调度”这条路从底层就被堵死了。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 每个连接池槽位必须绑定一个独占的
MYSQL*或PGconn*实例,且只由归属的工作线程或协程访问 - 避免在连接上做跨线程传递;若需异步回调,应把结果拷贝后传递,而非传递原始连接指针
- MySQL 官方推荐用
mysql_thread_init/mysql_thread_end配合线程局部存储(TLS),但更稳妥的做法是直接用std::thread_local管理连接句柄生命周期
boost.asio 和 libpq 异步接口怎么配合用才不翻车
PostgreSQL 的 libpq 提供了非阻塞模式(PQsetnonblocking)和事件轮询(PQsocket + select/poll/epoll),但它本身不提供异步回调机制;而 boost.asio 是通用 I/O 框架,不能直接理解 SQL 协议语义。两者硬拼容易卡在“连接已建立但查询没发出去”或“结果就绪但没及时读”这种状态里。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
boost::asio::posix::stream_descriptor封装PQsocket(conn),但仅用于监听可读/可写事件,不接管协议解析 - 每次事件触发后,必须严格按
libpq状态机调用:PQisBusy→PQconsumeInput→PQisBusy→PQgetResult,漏一步就丢结果 - MySQL 没原生异步支持,别试图用
boost.asio直接包装mysql_real_connect;改用mysql_real_connect同步建连 +boost::asio::post把耗时操作扔进线程池更稳
连接池大小设成 CPU 核数的 2 倍真的合理吗
不合理。这个经验值只适用于纯计算型任务,在数据库场景下,连接池瓶颈往往不在 CPU,而在网络往返(RTT)、服务端连接数限制、事务持有时间,以及客户端驱动本身的缓冲区竞争。比如 PostgreSQL 默认 max_connections=100,你开 200 个客户端连接池毫无意义,反而触发服务端拒绝连接(错误信息:FATAL: sorry, too many clients already)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先看服务端配置:
SHOW max_connections(PG)或SHOW VARIABLES LIKE 'max_connections'(MySQL),客户端池大小建议 ≤ 70% 服务端上限 - 观察平均查询延迟:若 P95 > 100ms,说明连接争抢严重,该扩容;若大部分连接空闲时间 > 3s,说明池子过大,浪费 FD 和内存
- 对短查询(如主键查)可用较小池(8–16),对长事务或批量导入场景,宁可加超时控制(
wait_timeout/idle_in_transaction_session_timeout),也不盲目扩池
连接泄漏的典型信号和快速定位方法
最常见表现不是程序崩,而是数据库侧连接数缓慢上涨,直到某次批量请求触发连接耗尽(MySQL 错误:Too many connections;PG 错误:remaining connection slots are reserved for non-replication superuser connections)。根本原因往往是异常路径没归还连接,比如 try/catch 里忘了 pool.return_connection(conn),或 RAII 对象析构时抛异常中断了归还逻辑。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有连接获取必须用 RAII 包装(如
std::unique_ptr+ 自定义 deleter),且 deleter 内禁止抛异常 - 在连接池类中加原子计数器(
std::atomic_int in_use_),暴露in_use()接口,定期打点上报;突增即告警 - 开启 MySQL 的
show processlist或 PG 的SELECT * FROM pg_stat_activity,过滤state = 'idle'且backend_start超过 5 分钟的连接,基本就是泄漏源
真正难搞的是那些“半死连接”:TCP 连通但数据库连接已断(如服务端 kill 或网络闪断),客户端还傻等。这时候光靠心跳不够,得结合 mysql_ping 或 PQping 的实际响应码判断,而不是只看 socket 是否可写。











