34.4. 异步命令处理

PQexec功能足以在正常的同步应用程序中提交命令。但是,它有一些缺陷,对于某些用户来说可能很重要:

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

  • 由于 Client 端应用程序在 await 结果时会暂停执行,因此应用程序很难决定要取消正在执行的命令。 (可以通过 signal 处理程序来完成,但是不能这样做.)

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

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

不喜欢这些限制的应用程序可以改用PQexec的基础功能:PQsendQueryPQgetResult。还有PQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortal,可与PQgetResult一起使用,以分别复制PQexecParamsPQpreparePQexecPreparedPQdescribePreparedPQdescribePortal的功能。

  • PQsendQuery
    • 向服务器提交命令,而无需 await 结果。如果命令已成功分派,则返回 1,否则返回 0(在这种情况下,请使用PQerrorMessage获取有关失败的更多信息)。
int PQsendQuery(PGconn *conn, const char *command);

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

  • PQsendQueryParams
    • 向服务器提交命令和单独的参数,而无需 await 结果。
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一样,它不适用于 2.0 协议的连接,并且在查询字符串中仅允许一个命令。

  • PQsendPrepare
    • 发送请求以给定的参数创建准备好的语句,而无需 await 完成。
int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

这是PQprepare的异步版本:如果能够分派请求,则返回 1,否则返回 0.成功调用后,调用PQgetResult确定服务器是否成功创建了准备好的语句。函数的参数处理与PQprepare相同。像PQprepare一样,它不适用于 2.0 协议的连接。

  • PQsendQueryPrepared
    • 发送请求以给定的参数执行准备好的语句,而无需 await 结果。
int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

这类似于PQsendQueryParams,但是要执行的命令是通过命名先前准备的语句来指定的,而不是给出查询字符串。函数的参数与PQexecPrepared相同地处理。像PQexecPrepared一样,它不适用于 2.0 协议的连接。

  • PQsendDescribePrepared
    • 提交请求以获取有关指定的准备好的语句的信息,而无需 await 完成。
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

这是PQdescribePrepared的异步版本:如果能够分派请求,则返回 1,否则返回 0.通话成功后,致电PQgetResult以获取结果。函数的参数处理与PQdescribePrepared相同。像PQdescribePrepared一样,它不适用于 2.0 协议的连接。

  • PQsendDescribePortal
    • 提交请求以获取有关指定门户网站的信息,而无需 await 完成。
int PQsendDescribePortal(PGconn *conn, const char *portalName);

这是PQdescribePortal的异步版本:如果能够分派请求,则返回 1,否则返回 0.通话成功后,致电PQgetResult以获取结果。函数的参数处理与PQdescribePortal相同。像PQdescribePortal一样,它不适用于 2.0 协议的连接。

  • PQgetResult
    • await 先前的PQsendQueryPQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortal调用的下一个结果,并将其返回。命令完成后,将返回空指针,并且将不再有结果。
PGresult *PQgetResult(PGconn *conn);

PQgetResult必须重复调用,直到返回空指针,表明命令已完成。 (如果在未激活任何命令的情况下调用PQgetResult,则将立即返回一个空指针.)PQgetResult的每个非空结果都应使用前面所述的相同PGresult访问器函数进行处理。完成后,不要忘记用PQclear释放每个结果对象。注意,仅当命令处于活动状态并且PQconsumeInput尚未读取必要的响应数据时,PQgetResult才会阻塞。

Note

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

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

可以通过PQsendQueryPQgetResult获得的另一个经常需要的功能是一次连续检索大型查询结果。 Section 34.5中对此进行了讨论。

就其本身而言,调用PQgetResult仍将导致 Client 端阻塞,直到服务器完成下一个 SQL 命令为止。可以通过另外使用两个功能来避免这种情况:

  • PQconsumeInput
    • 如果服务器提供了 Importing,请使用它。
int PQconsumeInput(PGconn *conn);

PQconsumeInput通常返回 1 表示“无错误”,但如果遇到某种麻烦则返回 0(在这种情况下,可以咨询PQerrorMessage)。请注意,结果并未说明是否实际收集了任何 Importing 数据。调用PQconsumeInput之后,应用程序可以检查PQisBusy和/或PQnotifies以查看其状态是否已更改。

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

  • PQisBusy
    • 如果命令繁忙,则返回 1,即PQgetResult将阻止 awaitImporting。返回值 0 表示可以在不阻塞的情况下调用PQgetResult
int PQisBusy(PGconn *conn);

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

使用这些功能的典型应用程序将具有一个主循环,该主循环使用select()poll()await 必须响应的所有条件。条件之一将从服务器 Importing,可用select()表示由PQsocket标识的文件 Descriptors 上的可读数据。当主循环检测到 Importing 准备就绪时,应调用PQconsumeInput读取 Importing。然后,它可以调用PQisBusy,然后调用PQgetResult,如果PQisBusy返回 false(0)。它还可以调用PQnotifies来检测NOTIFY条消息(请参阅Section 34.8)。

使用PQsendQuery/PQgetResult的 Client 端也可以尝试取消服务器仍在处理的命令。参见Section 34.6。但是无论返回值PQcancel如何,应用程序都必须 continue 使用PQgetResult进行正常的结果读取序列。成功的取消只会导致命令比其他情况早终止。

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

  • PQsetnonblocking
    • 设置连接的非阻塞状态。
int PQsetnonblocking(PGconn *conn, int arg);

如果* arg 为 1,则将连接状态设置为非阻塞;如果 arg *为 0,则将连接状态设置为阻塞。如果 OK,则返回 0,如果错误,则返回-1.

在非阻塞状态下,对PQsendQueryPQputlinePQputnbytesPQputCopyDataPQendcopy的调用不会阻塞,而是在需要再次调用时返回错误。

请注意,PQexec不支持非阻塞模式;如果它被调用,它将以阻塞的方式起作用。

  • PQisnonblocking
    • 返回数据库连接的阻止状态。
int PQisnonblocking(const PGconn *conn);

如果连接设置为非阻塞模式,则返回 1;如果阻塞,则返回 0.

  • PQflush
    • 尝试将所有排队的输出数据刷新到服务器。如果成功(或发送队列为空),则返回 0;如果由于某种原因而失败,则返回-1;如果它仍无法发送发送队列中的所有数据,则返回 1(这种情况仅在连接未阻塞时发生) )。
int PQflush(PGconn *conn);

在非阻塞连接上发送任何命令或数据后,请调用PQflush。如果返回 1,请 await 套接字变为可读写状态。如果它变为可写状态,请再次调用PQflush。如果已准备就绪,请致电PQconsumeInput,然后再次致电PQflush。重复直到PQflush返回 0.(必须检查是否已准备好并用PQconsumeInput清空 Importing,因为服务器可以阻止尝试向我们发送数据(例如 NOTICE 消息),并且在我们读取数据之前不会读取我们的数据。 )PQflush返回 0 后,await 套接字准备就绪,然后如上所述读取响应。