2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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命令,则PQexec会丢弃除最后一个PGresult之外的所有命令。

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

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

PQsendQuery #

在不等待结果的情况下向服务器提交命令。如果命令已成功调度,则返回1,如果未成功,则返回0(在这种情况下,使用PQerrorMessage获取有关失败的更多信息)。

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

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

在管道模式下,此函数是被禁止的。

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 #

提交一个请求,以获取有关指定portal的信息,而不等待其完成。

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 #

提交一个请求,以关闭指定的portal,而不等待其完成。

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

这是PQclosePortal的异步版本:如果它能够调度请求,则返回1,否则返回0。成功调用后,请调用PQgetResult以获取结果。该函数的参数处理方式与PQclosePortal相同。

PQgetResult #

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

PGresult *PQgetResult(PGconn *conn);

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

在管道模式下,PQgetResult将正常返回,除非发生错误;对于在导致错误的查询之后发送的任何后续查询(直到下一个同步点,但不包括它),将返回一种特殊的PGRES_PIPELINE_ABORTED类型的结果,然后返回null指针。当达到管道同步点时,将返回PGRES_PIPELINE_SYNC类型的结果。同步点之后的下一个查询的结果会立即跟上(也就是说,同步点之后不会返回null指针)。

注意

即使PQresultStatus指示发生致命错误,也应调用PQgetResult直到它返回null指针,以允许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,然后调用PQgetResult(如果PQisBusy返回false(0))。它还可以调用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,请等待套接字可读,然后如上所述读取响应。

提交更正

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