PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

32.4. 异步命令处理 #

对于在普通的、同步的应用程序中提交命令来说, PQexec 函数已经足够。 不过,它有一些缺陷, 可能对某些用户很重要:

不喜欢这些限制的应用程序可以改用构建 PQexec 所用的底层函数: PQsendQuery 和 PQgetResult。 还有 PQsendQueryParams、 PQsendPrepare、 PQsendQueryPrepared、 PQsendDescribePrepared、 PQsendDescribePortal、 PQsendClosePrepared 和 PQsendClosePortal, 它们可以与 PQgetResult 一起使用, 分别复刻 PQexecParams、 PQprepare、 PQexecPrepared、 PQdescribePrepared、 PQdescribePortal、 PQclosePrepared 和 PQclosePortal 的功能。

PQsendQuery #

把一条命令提交给服务器, 而不等待结果。 如果命令被成功分派则返回 1, 否则返回 0 (在这种情况下, 使用 PQerrorMessage 获取关于失败的更多信息)。

int PQsendQuery(PGconn *conn, const char *command);

在成功调用 PQsendQuery 之后, 调用 PQgetResult 一次或多次来获取结果。 在 PQgetResult 返回一个空指针(表示命令已完成)之前, 不能(在同一条连接上)再次调用 PQsendQuery。

在管道模式下, 这个函数是不被允许的。

PQsendQueryParams #

把一条命令及其单独的参数提交给服务器, 而不等待结果。

int PQsendQueryParams(PGconn *conn,
                      const char *command,
                      int nParams,
                      const Oid *paramTypes,
                      const char * const *paramValues,
                      const int *paramLengths,
                      const int *paramFormats,
                      int resultFormat);

它等同于 PQsendQuery, 只是查询参数可以与查询字符串分开指定。 该函数的参数的处理方式与 PQexecParams 完全相同。 与 PQexecParams 一样, 它只允许查询字符串中有一条命令。

PQsendPrepare #

发送一个用给定参数创建已准备语句的请求, 而不等待完成。

int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

它是 PQprepare 的一个异步版本: 如果它能够分派该请求则返回 1, 否则返回 0。 在一次成功的调用之后, 调用 PQgetResult 来确定服务器是否成功创建了该已准备语句。 该函数的参数的处理方式与 PQprepare 完全相同。

PQsendQueryPrepared #

发送一个用给定参数执行已准备语句的请求, 而不等待结果。

int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

它类似于 PQsendQueryParams, 但要执行的命令是通过命名一个先前准备好的语句来指定的, 而非给出一个查询字符串。 该函数的参数的处理方式与 PQexecPrepared 完全相同。

PQsendDescribePrepared #

提交一个获取关于指定已准备语句信息的请求, 而不等待完成。

int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

它是 PQdescribePrepared 的一个异步版本: 如果它能够分派该请求则返回 1, 否则返回 0。 在一次成功的调用之后, 调用 PQgetResult 来获取结果。 该函数的参数的处理方式与 PQdescribePrepared 完全相同。

PQsendDescribePortal #

提交一个获取关于指定游标信息的请求, 而不等待完成。

int PQsendDescribePortal(PGconn *conn, const char *portalName);

它是 PQdescribePortal 的一个异步版本: 如果它能够分派该请求则返回 1, 否则返回 0。 在一次成功的调用之后, 调用 PQgetResult 来获取结果。 该函数的参数的处理方式与 PQdescribePortal 完全相同。

PQsendClosePrepared #

提交一个关闭指定已准备语句的请求, 而不等待完成。

int PQsendClosePrepared(PGconn *conn, const char *stmtName);

它是 PQclosePrepared 的一个异步版本: 如果它能够分派该请求则返回 1, 否则返回 0。 在一次成功的调用之后, 调用 PQgetResult 来获取结果。 该函数的参数的处理方式与 PQclosePrepared 完全相同。

PQsendClosePortal #

提交一个关闭指定游标的请求, 而不等待完成。

int PQsendClosePortal(PGconn *conn, const char *portalName);

它是 PQclosePortal 的一个异步版本: 如果它能够分派该请求则返回 1, 否则返回 0。 在一次成功的调用之后, 调用 PQgetResult 来获取结果。 该函数的参数的处理方式与 PQclosePortal 完全相同。

PQgetResult #

等待来自之前一次 PQsendQuery、 PQsendQueryParams、 PQsendPrepare、 PQsendQueryPrepared、 PQsendDescribePrepared、 PQsendDescribePortal、 PQsendClosePrepared、 PQsendClosePortal、 PQsendPipelineSync 或 PQpipelineSync 调用的下一个结果, 并返回它。 当命令完成且不会再有更多结果时, 返回一个空指针。

PGresult *PQgetResult(PGconn *conn);

必须反复调用 PQgetResult 直到它返回一个空指针, 这表示命令已完成。 (如果在没有活动命令时调用它, PQgetResult 将立即返回一个空指针。) 来自 PQgetResult 的每个非空结果都应当使用之前描述的相同 PGresult 访问器函数来处理。 处理完之后别忘了用 PQclear 释放每个结果对象。 注意, 只有当一条命令处于活动状态且必要的响应数据尚未被 PQconsumeInput 读取时, PQgetResult 才会阻塞。

在管道模式中, 除非发生错误, PQgetResult 将正常返回; 对于在引起错误的那个查询之后发送的、 直到(但不包括)下一个同步点的任何后续查询, 都会返回一个类型为 PGRES_PIPELINE_ABORTED 的特殊结果, 并且在它之后会返回一个空指针。 当到达管道同步点时, 会返回一个类型为 PGRES_PIPELINE_SYNC 的结果。 同步点之后的下一个查询的结果会立即跟着出现 (也就是说, 在同步点之后不会返回空指针)。

Note

即便 PQresultStatus 指示一个致命错误, 也应当继续调用 PQgetResult 直到它返回一个空指针, 以让 libpq 完全处理错误信息。

使用 PQsendQuery 和 PQgetResult 解决了 PQexec 的其中一个问题: 如果一条命令字符串包含多条 SQL 命令, 这些命令的结果可以被单独获取。 (顺带一提, 这允许一种简单的重叠处理形式: 在服务器仍在处理同一命令字符串中的后续查询时, 客户端可以正在处理其中一条命令的结果。)

通过 PQsendQuery 和 PQgetResult 可以获得的另一个经常被期望的特性是: 一次只以有限数量的行检索大型查询结果。 这将在 Section 32.6 中讨论。

就其本身而言, 调用 PQgetResult 仍然会导致客户端阻塞, 直到服务器完成下一条 SQL 命令。 通过正确地使用另外两个函数可以避免这种情况:

PQconsumeInput #

如果来自服务器的输入可用, 则消费它。

int PQconsumeInput(PGconn *conn);

PQconsumeInput 通常返回 1 表示 “没有错误”, 但如果出现了某种问题则返回 0 (在这种情况下可以查阅 PQerrorMessage )。 注意,该结果并不说明是否实际收集到了任何输入数据。 在调用 PQconsumeInput 之后, 应用程序可以检查 PQisBusy 和/或 PQnotifies 来看看它们的状态是否发生了变化。

即使应用程序尚未准备好处理某个结果或通知, 也可以调用 PQconsumeInput 。 该函数会读取可用的数据并把它保存在一个缓冲区中, 从而让 select() 的读就绪指示消失。 因此, 应用程序可以用 PQconsumeInput 立即清除 select() 条件, 然后在闲暇时检查结果。

PQisBusy #

如果一条命令正忙, 即 PQgetResult 将阻塞等待输入, 则返回 1。 返回 0 表示可以放心地调用 PQgetResult 而不会阻塞。

int PQisBusy(PGconn *conn);

PQisBusy 自身不会尝试从服务器读取数据; 因此必须先调用 PQconsumeInput , 否则忙状态永远不会结束。

使用这些函数的一个典型应用程序会有一个主循环, 它使用 select() 或 poll() 来等待它必须响应的所有条件。 其中一个条件是来自服务器的输入可用, 用 select() 的术语来说, 这意味着由 PQsocket 标识的文件描述符上有可读数据。 当主循环检测到输入就绪时, 它应当调用 PQconsumeInput 来读取输入。 然后它可以调用 PQisBusy, 如果 PQisBusy 返回假(0), 再接着调用 PQgetResult。 它还可以调用 PQnotifies 来检测 NOTIFY 消息(参见 Section 32.9)。

使用 PQsendQuery/PQgetResult 的客户端也可以尝试取消一条仍被服务器处理的命令; 参见 Section 32.7。 但无论 PQcancelBlocking 的返回值如何, 应用程序都必须使用 PQgetResult 继续正常的结果读取序列。 一次成功的取消只会让命令比原本更早地终止。

通过使用上述函数, 可以避免在等待来自数据库服务器的输入时阻塞。 然而, 应用程序仍有可能在等待向服务器发送输出时阻塞。 这种情况相对不常见, 但如果发送了很长的 SQL 命令或数据值, 它就可能发生。 (不过, 如果应用程序通过 COPY IN 发送数据, 它发生的可能性会大得多。) 为了防止这种可能性并实现完全非阻塞的数据库操作, 可以使用以下额外的函数。

PQsetnonblocking #

设置连接的非阻塞状态。

int PQsetnonblocking(PGconn *conn, int arg);

如果 arg 为 1, 把连接的状态设置为非阻塞; 如果 arg 为 0, 则设置为阻塞。 成功返回 0,出错返回 -1。

在非阻塞状态下, 对 PQsendQuery、 PQputline、 PQputnbytes、 PQputCopyData 和 PQendcopy 的成功调用将不会阻塞; 它们的数据被存储在本地输出缓冲区中, 直到它们被刷出。 不成功的调用会返回一个错误, 并且必须重试。

注意,PQexec 不遵守非阻塞模式; 如果调用它, 无论如何它都会以阻塞的方式行动。

PQisnonblocking #

返回数据库连接的阻塞状态。

int PQisnonblocking(const PGconn *conn);

如果连接被设置为非阻塞模式则返回 1, 如果是阻塞模式则返回 0。

PQflush #

尝试把任何排队中的输出数据刷到服务器。 成功时(或发送队列为空时)返回 0, 因某种原因失败时返回 -1, 或者当它尚未能把发送队列中的所有数据都发送出去时返回 1 (这种情况只在连接是非阻塞的时候才可能出现)。

int PQflush(PGconn *conn);

在非阻塞连接上发送了任何命令或数据之后, 调用 PQflush。 如果它返回 1, 则等待套接字变为读就绪或写就绪。 如果它变为写就绪, 再次调用 PQflush。 如果它变为读就绪, 调用 PQconsumeInput , 然后再次调用 PQflush。 重复这一过程, 直到 PQflush 返回 0。 (有必要检查读就绪并用 PQconsumeInput 排空输入, 因为服务器可能在尝试向我们发送数据时阻塞, 例如 NOTICE 消息, 并且在我们读取它的数据之前它不会读取我们的数据。) 一旦 PQflush 返回 0, 等待套接字变为读就绪, 然后按上述方式读取响应。