支持的版本: 当前 (17) / 16 / 15 / 14 / 13
开发版本: 开发版
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

32.4. 异步命令处理 #

PQexec 函数足以在普通的同步应用程序中提交命令。然而,它有一些缺陷,这些缺陷对某些用户来说可能很重要。

  • PQexec 会等待命令完成。应用程序可能还有其他工作要做(例如维护用户界面),在这种情况下,它不希望阻塞等待响应。

  • 由于客户端应用程序在等待结果时会被挂起,因此应用程序很难决定是否想要尝试取消正在进行的命令。(这可以从信号处理程序中完成,但其他方式不行。)

  • PQexec 只能返回一个 PGresult 结构。如果提交的命令字符串包含多个SQL命令,则除了最后一个 PGresult 之外,所有 PGresult 都会被 PQexec 丢弃。

  • PQexec 总是收集命令的全部结果,并将其缓冲到一个 PGresult 中。虽然这简化了应用程序的错误处理逻辑,但对于包含许多行的结果来说,这可能是不切实际的。

不喜欢这些限制的应用程序可以改用 PQexec 构建所基于的底层函数:PQsendQueryPQgetResult。还有 PQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortalPQsendClosePreparedPQsendClosePortal,它们可以与 PQgetResult 一起使用,以复制 PQexecParamsPQpreparePQexecPreparedPQdescribePreparedPQdescribePortalPQclosePreparedPQclosePortal 的功能。

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 #

等待先前 PQsendQueryPQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortalPQsendClosePreparedPQsendClosePortalPQsendPipelineSyncPQpipelineSync 调用的下一个结果,并返回它。当命令完成并且没有更多结果时,返回一个空指针。

PGresult *PQgetResult(PGconn *conn);

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

在流水线模式下,除非发生错误,否则 PQgetResult 将正常返回;对于导致错误的查询之后发送的任何后续查询,直到(但不包括)下一个同步点,将返回 PGRES_PIPELINE_ABORTED 类型的特殊结果,之后将返回空指针。当到达流水线同步点时,将返回 PGRES_PIPELINE_SYNC 类型的结果。同步点之后的下一个查询的结果紧随其后(也就是说,同步点之后不会返回空指针。)

注意

即使PQresultStatus指示发生致命错误,也应调用PQgetResult直到它返回空指针,以允许 libpq 完全处理错误信息。

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

使用PQsendQueryPQgetResult可以获得的另一个常见需求是一次检索有限行数的大型查询结果。这在第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返回false (0),则调用PQgetResult。它还可以调用PQnotifies来检测NOTIFY消息(请参阅第 32.9 节)。

使用PQsendQuery/PQgetResult的客户端还可以尝试取消服务器仍在处理的命令;请参阅第 32.7 节。但是,无论PQcancelBlocking的返回值如何,应用程序都必须使用PQgetResult继续正常的读取结果序列。成功取消只会导致命令比原本更快地终止。

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

PQsetnonblocking #

设置连接的非阻塞状态。

int PQsetnonblocking(PGconn *conn, int arg);

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

在非阻塞状态下,成功调用PQsendQueryPQputlinePQputnbytesPQputCopyDataPQendcopy不会阻塞;它们的更改会存储在本地输出缓冲区中,直到它们被刷新。不成功的调用将返回错误,必须重试。

请注意,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,则等待套接字变为可读就绪,然后按照上述方式读取响应。

提交更正

如果您在文档中发现任何不正确、与特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。