应使用连接池而非频繁调用mysql_real_connect和mysql_close,因高频短连接会导致握手开销剧增、max_connections打满、time_wait堆积及mysql server has gone away等问题。

直接用连接池,别每次查库都 mysql_real_connect 再 mysql_close —— 这是高频短连接场景下最典型的性能误操作。
为什么裸连接在多线程下会雪崩
每个线程自己建连,等于把 MySQL 的握手开销(TCP 三次握手 + 认证 + 初始化会话)乘以线程并发数。实测内网环境下单次连接建立要 1–3ms,而一个简单查询本身才 0.5ms;100 个线程同时抢连,max_connections 很快打满,服务端大量 Too many connections 报错,客户端开始超时重试,形成恶性循环。
- Linux 上还会堆积大量
TIME_WAIT状态连接,占用文件描述符 - MySQL 的
wait_timeout(默认 8 小时)对连接池无效——池中空闲连接可能早被服务端静默断开,下次取出直接报MySQL server has gone away - 多线程争抢全局连接资源时,若没加锁或锁粒度太粗,会出现线程阻塞、虚假超时
ConnectionPool 必须支持的三个并发控制点
不是简单用 std::queue + std::mutex 就完事。真实高并发下,这三个地方不处理好,池子反而成瓶颈:
- 获取连接时不能只
pop:得先检查连接是否还活着(例如执行SELECT 1),否则归还失效连接会导致后续线程拿到就失败 - 归还连接不能无脑
push:要判断连接是否超时闲置(比如 > 60s),避免长期挂起的连接占着坑位却不干活 - 扩容不能无限制:当空闲连接不足,需创建新连接时,必须用原子计数器控制当前总连接数 ≤
maxOpen,否则照样触发Too many connections
推荐用 std::condition_variable 配合超时等待(wait_for),而不是死等 —— 避免某个线程卡住整个池子。
RAII 封装 ConnectionGuard 是防泄漏的关键
多线程环境下,任何异常路径(比如 SQL 执行中途抛 std::runtime_error)都可能导致连接忘记归还。裸指针管理几乎必然出问题。
- 构造时从池中取连接,析构时自动归还(不管是否异常退出)
- 禁止拷贝(
ConnectionGuard(const ConnectionGuard&) = delete),只允许移动语义 - 内部持有的连接对象本身也应是 RAII 封装的(如基于
mysqlcppconn的sql::Connection智能指针),避免底层 C API 资源泄漏
示例片段:
class ConnectionGuard {
public:
explicit ConnectionGuard(ConnectionPool& pool) : conn_(pool.getConnection()) {}
~ConnectionGuard() { if (conn_) conn_->returnToPool(); }
sql::Connection* operator->() { return conn_.get(); }
private:
std::unique_ptr<:connection> conn_;
};</:connection>
容易被忽略的冷启动与健康检查细节
连接池初始化时预热连接数(minIdle)≠ 所有连接都能立刻用。云数据库(如阿里云 RDS、AWS RDS)常有 DNS 缓存、SSL 握手延迟、代理层拦截等问题,导致首批连接看似成功,实际执行第一条 SQL 就失败。
- 预热阶段必须执行一次真实查询(如
SELECT VERSION()),而不仅是ping - 后台需有独立线程定期探测空闲连接健康状态,不能只依赖“取用前检查”——因为取用前检查本身也会失败,且增加延迟
- 连接字符串里的
connect_timeout和read_timeout必须显式设小(如 2s),否则一个卡死的连接会让整个线程卡住
真正难的不是写个队列,而是让每个连接在生命周期里始终处于“可执行 SQL”的状态——这需要网络、数据库参数、C++ 异常模型三者咬合严密。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











