【C++三方组件】libpqxx:PostgreSQL 的 C++ 客户端
【摘要】:libpqxx 是 PostgreSQL 官方维护的 C++ 客户端:connection、work(事务)、result 三个对象撑起全部日常;参数化查询以 $1 占位、预编译语句、异常错误体系原生到位;事务 RAII 化------不 commit 就回滚,中途抛异常也不怕。本篇实测参数化插入、回滚、预编译语句、sql_error 与批量插入吞吐;两个必踩点:一条连接同时只能有一个事务、8.0 起要求 C++20。第 39 篇 MySQL 客户端与本篇逐节同构,可对照读。
【关键词】:libpqxx、PostgreSQL、参数化查询、事务、预编译语句
【版本基准】:libpqxx 8.0.0(BSD-3-Clause)+ PostgreSQL 18 服务端|示例 C++20------8.0 起要求 C++20(头文件使用 source_location、ranges)|实测环境:本机 PG 18.0.4 临时实例 @ 127.0.0.1:5433
1. What:官方 C++ 客户端的三对象世界
libpqxx(jtv/libpqxx,pqxx.org)是 PostgreSQL 官方文档钦点的 C++ API,站在官方 C 库 libpq 之上。它的一切围绕三个对象:
pqxx::connection:一条到服务端的连接(连接串一次配齐);pqxx::work:一个事务 ------构造即BEGIN,commit()提交,析构未提交即回滚;pqxx::result:查询结果集,按行按列访问。
「一切在事务里」是 libpqxx 的世界观:没有裸执行 SQL 的接口,哪怕一条 SELECT 也开只读事务跑。这对从裸连接概念过来的读者是第一道弯------但换来的纪律是:忘记提交不可能发生半提交,异常路径自动回滚(第 6 节实测)。
版本上注意:8.0 起要求 C++20(GCC ≥ 11 / Clang ≥ 10 / MSVC v143+),7.10.x 是 C++17 时代的末代版本------锁 C++17 的项目两个都行,新项目直接 8.0。
2. Why:libpq 之上还缺的一层
libpq 本身功能完备,但它是 90 年代风格的 C API:PQexecParams 返回 PGresult*,PQgetvalue 按行列下标取 C 字符串,PQclear 手动归还,错误码在 PQresultErrorField 里翻------每个查询七件套 (构造参数数组、执行、查状态、循环取值、转换类型、清理、错误分支)。libpqxx 把七件套收进 work::exec_params + result 遍历 + 异常;再把事务生命周期 RAII 化------这是 libpq 里最容易写错的部分(BEGIN 后崩溃,连接复用时事务悬空),在 libpqxx 里被析构函数兜死。
3. How:接入与运行
- vcpkg :
vcpkg install libpqxx(自动带 libpq),CMake 走find_package(libpqxx CONFIG REQUIRED),目标是libpqxx::pqxx; - 源码 + PostgreSQL 安装目录 :源码
add_subdirectory,用-DPostgreSQL_ROOT指向本机 PG 安装(头文件与libpq库都在里面)------示例工程即此路线,本机 PG 18 的安装器产物直接复用。
⚠️ C++20 是硬要求(第 1 节),顶层工程语言标准不达标会报一堆 source_location 不是 std 的成员。
完整示例 pqxx_demo.cpp:一个 main 串七节。实测用独立于系统服务的临时实例 (initdb 初始化、trust 认证、端口 5433)------既不动系统数据库,也免去找管理员密码的麻烦,起停命令见 README。
4. 连接串与连接管理
cpp
pqxx::connection conn(
"host=127.0.0.1 port=5433 dbname=postgres"
" user=blogtest");
连接串是键值对:主机、端口、库、用户、密码、超时、SSL 模式......一张字符串配齐,URL 形式(postgresql://user@host/db)也认。conn.is_open() 查活;连接失败抛 pqxx::broken_connection (不返回错误码)。断线后这条 connection 作废、要重开------长驻服务通常自建小连接池(8.0 起库内也提供了 pqxx::connection_pool),新连接记得重做 prepare(第 7 节)。
5. 参数化查询:$1 占位
cpp
pqxx::work tx(conn); // BEGIN
tx.exec_params(
"INSERT INTO items VALUES($1, $2, $3)",
1, "item-1", 10);
$1/$2 占位、实参变参传入------值走协议层带类型传输,不存在 SQL 拼接 ,注入面从根上焊死(对照第 33 篇 SQLite 的 ?1 绑定:同一件事的两种占位符方言)。查询同样参数化:
cpp
auto rows = tx.exec_params(
"SELECT id, name, qty FROM items"
" WHERE qty >= $1 ORDER BY id", 20);
for (const auto& row : rows)
row["name"].c_str(); // 按列名
row[0].as<int>(); // 按下标 + 类型转换
result 的行可按列名或下标 取列,as<T>() 做转换、c_str() 拿字符串。单值查询有捷径 tx.query_value<long>("SELECT count(*) ...")------省掉取第一行第一列的两步。
6. 事务回滚:RAII 的实测
cpp
try {
pqxx::work bad(conn);
bad.exec_params("INSERT ...", 999, "ghost-row", 1);
throw std::runtime_error("业务校验失败");
} catch (const std::exception&) {
// bad 已析构:整段插入回滚
}
实测输出:事务抛出:业务校验失败(已自动回滚)、ghost-row 是否存在:否------没有一行显式 ROLLBACK ,析构函数替你做了。正向同理:commit() 之前的任何路径(异常、提前 return、进程崩溃)都不会留下半个事务。
必踩点 :一条 connection 同时只能挂一个 work。示例开发时实测:上一节的查询事务忘了收尾,下一节再开 work 直接崩溃。纪律是每个 work 用完立即 commit()(或用块作用域约束生命周期),一条直线代码里的事务不要跨越业务段落。
7. 预编译语句
cpp
conn.prepare("get_qty",
"SELECT qty FROM items WHERE id = $1");
pqxx::work ps(conn);
auto r = ps.exec_prepared("get_qty", 3);
prepare 挂在连接 上(一次解析、多次执行),exec_prepared 按名字调用------高频语句省掉每次的解析与规划。与 exec_params 的分工:偶发语句用 exec_params(免注册),热点语句用 prepare(连接生命周期内复用)。⚠️ 预编译语句跟着连接走 :连接断了重开,prepare 要重做;连接池场景要在借出前确认(或池初始化时统一注册)。
8. 错误体系与批量插入
异常分类捕(实测输出):
text
sql_error: 错误: 语法错误 在 "SELEKT" 或附近的
broken_connection: connection to server at "127.0.0.1",
port 15333 failed: Connection refused
pqxx::sql_error 携带服务端错误详情(SQLSTATE 可进一步细分);broken_connection 表示传输层断开------对照第 36 篇 hiredis 的双通道,这里是异常类的两个分支。批量口径实测(事务内逐条 exec_params,100 B value):
text
10000 条参数化插入:6121.5 ms(0.2 万条/秒)
每条一次网络往返的量级,与 MySQL 侧(第 39 篇)同一口径------要快用批量协议 :pqxx::stream_to(COPY 流)是 PG 的批量正道,万条以上再考虑。
9. 边界与选型
libpqxx 的边界就是 libpq 的边界:一条连接一次一个事务(并发靠多连接);同步 API(异步要靠连接池 + 线程,或另选 asyncpg 风格的库);编译模板较重。同为 PostgreSQL C++ 客户端还有 SOCI(多数据库后端的统一 facade,第 39 篇同款对照)------只写 PG 时 libpqxx 的 API 更贴 PG 语义(LISTEN/NOTIFY、COPY、管道都有直接接口)。
与第 39 篇的关系:MySQL Connector/C++ 的 JDBC 风格 API 与本篇逐节同构(连接 / 事务 / 参数化 / 结果集 / 异常),两篇刻意用同一组示例任务------下一节直接对照读,两套数据库客户端的写法差异会缩成一张表。
〔关联〕MVCC 事务隔离级别与 work 的映射、pipeline 模式(pqxx::pipeline)属深入话题,见 cpp-interview-faq;libpqxx 的事务对象层次(work 是 basic_transaction 的一种)设计剖析归 cpp-source-reading。
10. 参考资料
- 官方文档 libpqxx.readthedocs.io:8.0 的 API 手册与升级说明;
- 上游 jtv/libpqxx:
RELEASE_NOTES里 8.0 的破坏性变更清单(C++20 要求原文); - 完整构建与运行入口(含 PG 临时实例的 initdb / pg_ctl 起停):examples/38-39/README;
- 下一篇(第 39 篇):MySQL Connector/C++------与本篇同构的 JDBC 风格对照极。
参考 :jtv/libpqxx 8.0.0(BSD-3-Clause),服务端 PostgreSQL 18.0.4(本机临时实例 @ 5433)。本篇全部输出与吞吐(参数化、回滚、预编译、两类异常、批量插入)均为本机实测(MSVC v145/VS2026,Windows)。