23. C语言操作PostgreSQL数据库:libpq库安装、PQconnectdb、PQexec、结果处理

PostgreSQL 在工业界用得挺广的,尤其是那些对数据一致性要求高的场景。我最早接触它是在一个金融交易系统里,那时候项目组选型,最后敲定了 PG。说实话,刚开始用 C 语言调 libpq 库的时候,我也踩过不少坑。今天咱们就把这块儿彻底捋清楚。

23.1 libpq 库的安装与配置

libpq 是 PostgreSQL 官方的 C 语言客户端库。说白了,它就是一座桥,让你的 C 程序能跟 PG 数据库对话。

在 Ubuntu/Debian 上安装:

sudo apt-get update
sudo apt-get install libpq-dev

在 CentOS/RHEL 上安装:

sudo yum install postgresql-devel

在 macOS 上(用 Homebrew):

brew install postgresql

安装完之后,头文件在 /usr/include/postgresql/ 目录下,库文件在 /usr/lib/x86_64-linux-gnu/ 或者类似位置。

小提示: 我个人习惯用 pg_config --libdirpg_config --includedir 来确认路径,这样最保险,不会找错地方。

编译链接命令:

gcc -o myapp myapp.c -I/usr/include/postgresql -lpq

这里 -lpq 就是链接 libpq 库。如果你用 CMake,可以这样写:

find_package(PostgreSQL REQUIRED)
include_directories(${PostgreSQL_INCLUDE_DIRS})
target_link_libraries(myapp ${PostgreSQL_LIBRARIES})

23.2 核心流程:连接、执行、处理结果

libpq 的操作流程其实很固定,就三步:建立连接 → 执行 SQL → 处理结果。我画了个图,你看一眼就明白了。

libpq 操作 PostgreSQL 核心流程 PQconnectdb 建立数据库连接 PQexec 执行 SQL 语句 结果处理 解析查询结果 PQresultStatus 检查错误 PQfinish / PQclear 清理资源 连接 执行 结果处理 错误路径

23.3 PQconnectdb:建立连接

PQconnectdb 是 libpq 里最基础的函数。你给它一个连接字符串,它帮你连上数据库。

#include <stdio.h>
#include <libpq-fe.h>

int main() {
    const char *conninfo = 
        "host=localhost "
        "port=5432 "
        "dbname=mydb "
        "user=myuser "
        "password=mypass";

    PGconn *conn = PQconnectdb(conninfo);

    if (PQstatus(conn) != CONNECTION_OK) {
        fprintf(stderr, "连接失败: %s", PQerrorMessage(conn));
        PQfinish(conn);
        return 1;
    }

    printf("连接成功!\n");
    // ... 后续操作 ...
    PQfinish(conn);
    return 0;
}

这里有几个关键点:

  • 连接字符串:可以包含 host、port、dbname、user、password 等参数。你也可以用环境变量 PGHOSTPGPORT 等来设置。
  • PQstatus:检查连接状态。返回 CONNECTION_OK 才表示连接成功。
  • PQerrorMessage:出错了用它看具体原因。
  • PQfinish:用完一定要释放连接,不然会有内存泄漏。
注意: 我曾经在生产环境里见过一个 bug——连接失败后没有调用 PQfinish,导致连接句柄泄漏。服务器跑了三天,内存暴涨,最后 OOM 被 kill 了。所以记住:PQconnectdb 和 PQfinish 必须成对出现

23.4 PQexec:执行 SQL 语句

连接建立好了,接下来就是执行 SQL。PQexec 是最直接的方式,它把 SQL 字符串发给服务器,然后返回结果。

PGresult *res = PQexec(conn, "SELECT id, name FROM users LIMIT 5");
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
    fprintf(stderr, "查询失败: %s", PQerrorMessage(conn));
    PQclear(res);
    PQfinish(conn);
    return 1;
}

PQexec 返回一个 PGresult * 指针。你需要用 PQresultStatus 检查执行状态:

状态常量 含义
PGRES_COMMAND_OK INSERT、UPDATE、DELETE 等无返回行的命令执行成功
PGRES_TUPLES_OK SELECT 等有返回行的查询执行成功
PGRES_FATAL_ERROR 执行出错,用 PQerrorMessage 看详情
经验之谈: 我建议你每次调用 PQexec 后都检查状态。别偷懒。有一次我在项目里写了个批量插入,忘了检查状态,结果中间有一条数据违反唯一约束,后面的全没插进去,我还以为成功了。排查了半天才发现问题。

23.5 结果处理:获取数据

对于 SELECT 查询,拿到 PGresult 之后,你需要从中提取数据。libpq 提供了一组函数来做这件事。

int nrows = PQntuples(res);    // 行数
int ncols = PQnfields(res);    // 列数

for (int i = 0; i < nrows; i++) {
    for (int j = 0; j < ncols; j++) {
        const char *value = PQgetvalue(res, i, j);
        const char *colname = PQfname(res, j);
        printf("%s: %s\t", colname, value);
    }
    printf("\n");
}

常用结果处理函数:

  • PQntuples(res):获取结果行数
  • PQnfields(res):获取结果列数
  • PQfname(res, col_index):获取列名
  • PQgetvalue(res, row_index, col_index):获取某个单元格的值(返回字符串)
  • PQgetisnull(res, row_index, col_index):判断某个单元格是否为 NULL
  • PQclear(res):释放结果集内存
重要: PQgetvalue 返回的是 const char *,所有值都以字符串形式返回。如果你需要整数或浮点数,记得用 atoiatofstrtol 转换。另外,用完结果集一定要调用 PQclear,否则内存会一直涨。

23.6 完整示例:查询并打印用户表

下面是一个完整的例子,把上面讲的东西串起来:

#include <stdio.h>
#include <libpq-fe.h>

int main() {
    // 1. 连接数据库
    PGconn *conn = PQconnectdb("dbname=mydb user=myuser password=mypass");
    if (PQstatus(conn) != CONNECTION_OK) {
        fprintf(stderr, "连接失败: %s\n", PQerrorMessage(conn));
        PQfinish(conn);
        return 1;
    }

    // 2. 执行查询
    PGresult *res = PQexec(conn, "SELECT id, name, email FROM users ORDER BY id");
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {
        fprintf(stderr, "查询失败: %s\n", PQerrorMessage(conn));
        PQclear(res);
        PQfinish(conn);
        return 1;
    }

    // 3. 处理结果
    int rows = PQntuples(res);
    int cols = PQnfields(res);

    printf("共 %d 条记录:\n", rows);
    for (int i = 0; i < rows; i++) {
        int id = atoi(PQgetvalue(res, i, 0));
        const char *name = PQgetvalue(res, i, 1);
        const char *email = PQgetvalue(res, i, 2);
        printf("ID: %d, Name: %s, Email: %s\n", id, name, email);
    }

    // 4. 清理
    PQclear(res);
    PQfinish(conn);
    return 0;
}

23.7 避坑指南

最后,我把自己这些年踩过的坑总结一下,你写代码的时候多留个心眼:

  • 连接字符串别写死:我建议从配置文件或环境变量读取,方便切换开发/测试/生产环境。
  • 检查 PQstatus:连接成功后,不代表后续操作一定成功。网络波动、服务器重启都可能导致连接断开。
  • PQclear 别忘:每个 PQexec 返回的 PGresult 都要释放。忘了的话,内存泄漏会让你头疼。
  • 处理 NULL 值:用 PQgetisnull 判断,别直接拿 PQgetvalue 的结果去比较字符串 "NULL"。
  • SQL 注入:别用 sprintf 拼接 SQL。用 PQexecParams 做参数化查询,安全又高效。
曾经有一次: 我在一个数据迁移工具里,忘了在循环中 PQclear 结果集。迁移了 100 万条数据,内存涨到 2GB,程序直接崩了。从那以后,我每次写完 PQexec,下一行就写 PQclear,形成肌肉记忆。

好了,libpq 的基础操作就这些。你想想看,其实核心就三个函数:PQconnectdb、PQexec、PQclear。把这三个玩转了,PostgreSQL 的 C 语言编程你就入门了。


公众号:蓝海资料掘金营,微信deep3321