用 Hical + MySQL 5 分钟搭建 CRUD API(C++20 协程版)
C 访问数据库难吗2026 年不再难了。本文用 Hical 的协程 DB 中间件带你从零搭建一个完整的 MySQL CRUD API —— 连接池管理、事务自动提交/回滚、慢查询检测全部开箱即用代码比大多数 Python 教程还简洁。三种姿势对比方式代码量连接池异步防注入裸mysql_query多手动管理连接手写阻塞手拼字符串危险ORM如 ODB少但有运行时膨胀内置视实现而定安全Hical 协程中间件少原生协程内置非阻塞co_awaitPreparedStatementHical 走第三条路连接池是协程化的查询全部走 PreparedStatement 防注入事务在中间件层自动管理业务代码只关心 SQL 逻辑。环境准备构建启用数据库支持# Linux / macOScmake-Bbuild-DHICAL_WITH_DATABASEON-DCMAKE_BUILD_TYPERelease cmake--buildbuild -j$(nproc)# Windows (MSYS2 MINGW64)cmake-Bbuild-GNinja-DHICAL_WITH_DATABASEON-DCMAKE_BUILD_TYPERelease cmake--buildbuild# Windows (MSVC vcpkg)cmake-Bbuild-DHICAL_WITH_DATABASEON-DCMAKE_BUILD_TYPERelease\-DCMAKE_TOOLCHAIN_FILEC:/vcpkg/scripts/buildsystems/vcpkg.cmake cmake--buildbuild--configRelease依赖说明Boost 1.85DB 中间件需要 Boost.MySQL1.85 版引入any_connectionOpenSSLMySQL TLS 连接可选已是框架强依赖CMakeLists 里加一行即可target_link_libraries(my_app PRIVATE hical::hical_core) # HICAL_WITH_DATABASEON 时 hical_core 自动链接 Boost.MySQL建表 SQLCREATEDATABASEIFNOTEXISTSdemoCHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ci;USEdemo;CREATETABLEIFNOTEXISTSusers(idINTUNSIGNEDNOTNULLAUTO_INCREMENT,nameVARCHAR(64)NOTNULL,emailVARCHAR(128)NOTNULLUNIQUE,created_atDATETIMENOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id))ENGINEInnoDBDEFAULTCHARSETutf8mb4;完整 main.cpp约 80 行包含连接池初始化、中间件注册、4 个 CRUD 路由#includecore/HttpServer.h#includecore/Log.h#includedb/DbConfig.h#includedb/DbConnectionPool.h#includedb/DbMiddleware.h#includedb/MysqlConnection.husingnamespacehical;usingnamespacehical::db;// DTO用于 POST /users 的请求体structCreateUserReq{std::string name;std::string email;HICAL_JSON(CreateUserReq,name,REQUIRED(email))};intmain(){// 1. 配置数据库连接DbConfig dbCfg;dbCfg.host127.0.0.1;dbCfg.port3306;dbCfg.userroot;dbCfg.passwordyourpassword;dbCfg.databasedemo;dbCfg.minConnections2;dbCfg.maxConnections16;// 2. 创建连接池工厂函数由 MysqlConnection::makeFactory() 提供HttpServerserver(8080);autopoolstd::make_sharedDbConnectionPool(server.ioContext(),dbCfg,MysqlConnection::makeFactory());// 3. 注册 DB 中间件autoTransactiontrue每个请求自动开启/提交/回滚事务server.use(makeDbMiddleware(pool,DbMiddlewareOptions{.autoTransactiontrue,.injectPooltrue,}));// ── GET /users ──────────────────────────────────────────────────server.router().get(/users,[](constHttpRequestreq)-AwaitableHttpResponse{autoconngetDbConnection(req);autoresultco_awaitconn-query(SELECT id,name,email,created_at FROM users,{});boost::json::array arr;for(constautorow:result.rows){arr.push_back(boost::json::object{{id,row[0]},{name,row[1]},{email,row[2]},{created_at,row[3]},});}co_returnHttpResponse::json(arr);});// ── GET /users/{id} ─────────────────────────────────────────────server.router().get(/users/{id},[](constHttpRequestreq)-AwaitableHttpResponse{autoconngetDbConnection(req);autoresultco_awaitconn-query(SELECT id,name,email,created_at FROM users WHERE id ?,{req.param(id)});if(result.empty()){co_returnHttpResponse::notFound(user not found);}constautorowresult[0];co_returnHttpResponse::json(boost::json::object{{id,row[0]},{name,row[1]},{email,row[2]},{created_at,row[3]},});});// ── POST /users ──────────────────────────────────────────────────server.router().post(/users,[](constHttpRequestreq)-AwaitableHttpResponse{autobodyreq.readJsonCreateUserReq();autoconngetDbConnection(req);autoresultco_awaitconn-execute(INSERT INTO users(name, email) VALUES(?, ?),{body.name,body.email});co_returnHttpResponse::json(boost::json::object{{id,result.insertId},{name,body.name},});});// ── DELETE /users/{id} ───────────────────────────────────────────server.router().del(/users/{id},[](constHttpRequestreq)-AwaitableHttpResponse{autoconngetDbConnection(req);autoresultco_awaitconn-execute(DELETE FROM users WHERE id ?,{req.param(id)});if(result.affectedRows0){co_returnHttpResponse::notFound(user not found);}co_returnHttpResponse::noContent();});// 4. 初始化连接池并启动服务异步初始化必须在 start() 前完成hical::coSpawn(server.ioContext(),[pool]()-Awaitablevoid{co_awaitpool-init();HICAL_LOG_INFO(DB pool ready, idle{},pool-idleCount());});HICAL_LOG_INFO(Server listening on :8080);server.start();}快速测试# 创建用户curl-XPOST http://localhost:8080/users\-HContent-Type: application/json\-d{name:Alice,email:aliceexample.com}# {id:1,name:Alice}# 查询列表curlhttp://localhost:8080/users# [{id:1,name:Alice,email:aliceexample.com,created_at:2026-05-04 12:00:00}]# 查询单个curlhttp://localhost:8080/users/1# {id:1,name:Alice,email:aliceexample.com,created_at:...}# 删除curl-XDELETE http://localhost:8080/users/1# HTTP 204 No Content自动事务详解autoTransaction true的效果中间件在路由执行前自动BEGIN路由正常返回后自动COMMIT抛出异常时自动ROLLBACK。这意味着业务代码里完全不需要写事务控制。来看一个转账场景server.router().post(/transfer,[](constHttpRequestreq)-AwaitableHttpResponse{autoconngetDbConnection(req);// 扣款co_awaitconn-execute(UPDATE accounts SET balance balance - ? WHERE id ?,{100,req.param(from_id)});// 模拟业务异常中间件捕获后自动回滚上面的扣款不会落库throwstd::runtime_error(insufficient balance);// 加款不会执行到这里co_awaitconn-execute(UPDATE accounts SET balance balance ? WHERE id ?,{100,req.param(to_id)});co_returnHttpResponse::ok(ok);});异常路径下中间件的洋葱后置阶段会自动调用conn-rollback()无需业务层操心。如果需要手动控制事务例如部分提交将autoTransaction设为false然后自己调用co_awaitconn-beginTransaction();// ... 业务逻辑 ...co_awaitconn-commit();// 出错时:co_awaitconn-rollback();慢查询检测makeQueryLogMiddleware必须在makeDbMiddleware之后注册它依赖请求中已注入的连接#includedb/DbQueryLog.h// 先注册 DB 中间件server.use(makeDbMiddleware(pool,{.autoTransactiontrue}));// 再注册查询日志中间件server.use(makeQueryLogMiddleware(QueryLogOptions{// 每个请求完成后汇总打印所有查询.onRequestComplete[](constHttpRequestreq,conststd::vectorQueryLogEntryentries){for(constautoe:entries){HICAL_LOG_DEBUG([query] {} | {}μs | {} rows,e.sql,e.duration.count(),e.rowCount);}},// 慢查询阈值超过 100ms 实时触发回调.slowQueryThresholdstd::chrono::milliseconds(100),.onSlowQuery[](constQueryLogEntrye){HICAL_LOG_WARN([slow query] {}ms | sql{},e.duration.count()/1000,e.sql);},}));QueryLogEntry的字段字段类型说明sqlstd::string原始 SQL 文本durationstd::chrono::microseconds执行耗时rowCountsize_t返回行数SELECTaffectedRowsuint64_t影响行数DMLisParameterizedbool是否使用了参数化查询连接池参数调优参数默认值推荐场景minConnections2低流量服务保持 2-4避免冷启动延迟maxConnections16单机 Web 服务通常 16-32不超过 MySQLmax_connections的 1/4idleTimeout300sMySQL 默认wait_timeout8h设 5 分钟绰绰有余acquireTimeout5s池满等待上限超时返回 503 而非无限阻塞queryTimeout30s防止慢查询耗尽所有连接OLTP 场景建议设 3-5shealthCheckInterval30s定期 ping 空闲连接防止 MySQL 服务端主动断开pingGracePeriod15s距上次 ping 不超过 15s 则跳过减少不必要的往返stmtCacheSize64每连接 LRU 缓存容量见下节高并发写入场景参考配置dbCfg.minConnections4;dbCfg.maxConnections32;dbCfg.acquireTimeoutstd::chrono::seconds{3};dbCfg.queryTimeoutstd::chrono::seconds{5};dbCfg.healthCheckIntervalstd::chrono::seconds{20};PreparedStatement 缓存stmtCacheSize每次query()/execute()调用都会走 PreparedStatement —— 这是 Hical 防 SQL 注入的核心机制。但每次都向 MySQL 发送PREPARE请求会有网络往返开销。stmtCacheSize控制每个连接维护的 LRU 缓存容量默认 64 条首次执行某条 SQL → 发送PREPARE缓存句柄后续执行同一 SQL → 直接复用缓存句柄零额外往返缓存满时 → 淘汰最久未使用的语句LRU释放资源dbCfg.stmtCacheSize128;// 路由多、SQL 种类多时可调大dbCfg.stmtCacheSize0;// 0 禁用缓存每次重新 PREPARE测试用实测对同一 SQL 的第二次调用耗时从 ~1.2ms含 PREPARE RTT降到 ~0.3ms纯执行。在 QPS 较高的接口上效果明显。总结Hical 的 DB 中间件把连接池、事务、PreparedStatement 三件最繁琐的事情统一封装让 C20 协程写出的数据库代码和 Go/Python 一样直白co_await conn-query(sql, params)取结果中间件自动搞定其余一切。GitHub: https://github.com/Hical61/Hical