【C++三方组件】libpqxx:PostgreSQL 的 C++ 客户端

【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:接入与运行

  1. vcpkg :vcpkg install libpqxx(自动带 libpq),CMake 走 find_package(libpqxx CONFIG REQUIRED),目标是 libpqxx::pqxx;
  2. 源码 + 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)。

相关推荐
H.莓飛1 小时前
【C++】内存管理
linux·开发语言·jvm·c++·visual studio
tachibana21 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
瀚高PG实验室1 小时前
业务无法通过hgproxy访问数据库
数据库·postgresql·瀚高数据库
心仪久了会沦陷2 小时前
C语言数据结构入门——队列补充:循环队列的数组实现
c语言·开发语言·数据结构·笔记
源图客2 小时前
Python环境安装与PyCharm的第一个项目
开发语言·python·pycharm
longlongzihan2 小时前
从LeetCode 19. 删除链表的倒数第 N 个结点深入理解“哨兵节点”
c++·算法·leetcode·链表
zx_741484812 小时前
PyQt5 环境搭建保姆级教程:PyCharm + QtDesigner + PyUIC 完整配置
开发语言·qt·pycharm·pyqt
爱吃提升2 小时前
Dev-C++ 5.11 完整版快捷键大全(信奥/初学适用)
c语言·c++
Soari2 小时前
交换机转发模式
开发语言·网络·php