Postgresql 中文操作指南

34.4. Asynchronous Command Processing #

PQexec 函数足以在正常的同步应用中提交命令。但是,它有几个缺点,对某些用户来说可能会很重要:

不喜欢这些限制的应用程序可以改为使用 PQexec 中内置的基本函数: PQsendQueryPQgetResult 。还有 PQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortal ,它们可以与 PQgetResult 一起使用,分别复制 PQexecParamsPQpreparePQexecPreparedPQdescribePreparedPQdescribePortal 的功能。

  • 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);
PGresult *PQgetResult(PGconn *conn);
  • 必须反复调用 PQgetResult ,直到它返回空指针,表示命令已完成。(如果没有命令处于活动状态时调用, PQgetResult 将立即返回空指针。)应使用先前描述的同一 PGresult 访问器函数处理 PQgetResult 中的每个非空结果。处理完 PQclear ,不要忘记释放每个结果对象。请注意,仅当命令处于活动状态并且 PQconsumeInput 尚未读取必要的响应数据时, PQgetResult 才会阻塞。

  • 在管道模式下,PQgetResult 将正常返回,除非出现错误;对于在导致错误的那个查询之后发送的任何后续查询(直到下一个同步点,不包括下一个同步点),将返回一个特殊 PGRES_PIPELINE_ABORTED 类型的结果,并在其之后返回一个空指针。达到管道同步点时,将返回 PGRES_PIPELINE_SYNC 类型的结果。同步点之后的下一个查询的结果紧随其后(即,同步点之后不会返回空指针)。

Note

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

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

PQsendQueryPQgetResult 提供的另一个常用的优势是,可逐行检索大量的查询结果。 Section 34.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 消息(请参阅 Section 34.9 )。

使用 PQsendQuery / PQgetResult 的客户端也可以尝试取消仍在由服务器处理的命令;请参阅 Section 34.7 。但是,无论 PQcancel 的返回值如何,应用程序都必须使用 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,请等待套接字变为就绪以进行读取,然后按照上述说明读取响应。