选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

34.4. 异步命令处理 #

PQexec函数足以满足普通同步应用程序提交命令的需要。不过,它有一些缺点,对某些用户可能很重要:

  • PQexec会等待命令完成。该应用可能有其他的工作要做(例如维护用户界面),这时它将不希望阻塞等待回应。

  • 因为客户端应用的执行在它等待结果时会被挂起,对于应用来说很难决定要不要尝试取消正在进行的命令(这可以在一个信号处理器中完成,但别无他法)。

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

  • PQexec总是收集命令的整个结果,把它缓存在一个单一的PGresult中。虽然这简化了应用的错误处理逻辑,它对于包含很多行的结果并不现实。

如果应用程序不希望受到这些限制,可以改用构成PQexec的底层函数:PQsendQueryPQgetResult。此外,还有PQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePrepared,以及PQsendDescribePortal,它们可以与PQgetResult配合使用,分别实现以下函数的功能:PQexecParamsPQpreparePQexecPreparedPQdescribePrepared,以及PQdescribePortal

PQsendQuery #

向服务器提交命令,不等待结果。命令发送成功时返回 1,否则返回 0(此时可以使用PQerrorMessage取得更多失败信息)。

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

成功调用PQsendQuery之后,应调用PQgetResult一次或多次来取得结果。PQsendQuery在同一连接上不能再次调用,直到PQgetResult返回空指针,表明命令已经完成。

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一样,它不能用于协议 2.0 的连接,并且查询字符串中只允许包含一条命令。

PQsendPrepare #

发送按给定参数创建预备语句的请求,不等待完成。

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

这是PQprepare的异步版本:请求发送成功时返回 1,否则返回 0。调用成功后,再调用PQgetResult,确定服务器是否成功创建了预备语句。函数参数的处理方式与PQprepare相同。与PQprepare一样,它不能用于协议 2.0 的连接。

PQsendQueryPrepared #

发送使用给定参数执行预备语句的请求,不等待结果。

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 #

提交获取指定预备语句信息的请求,不等待完成。

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

这是PQdescribePrepared的异步版本:请求发送成功时返回 1,否则返回 0。调用成功后,再调用PQgetResult获取结果。函数参数的处理方式与PQdescribePrepared相同。与PQdescribePrepared一样,它不能用于协议 2.0 的连接。

PQsendDescribePortal #

提交获取指定 portal 信息的请求,不等待完成。

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

这是PQdescribePortal的异步版本:请求发送成功时返回 1,否则返回 0。调用成功后,再调用PQgetResult获取结果。函数参数的处理方式与PQdescribePortal相同。与PQdescribePortal一样,它不能用于协议 2.0 的连接。

PQgetResult #

等待先前的PQsendQueryPQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePrepared,或PQsendDescribePortal调用产生的下一个结果,并返回该结果。当命令执行完毕且不再有其他结果时,返回空指针。

PGresult *PQgetResult(PGconn *conn);

必须反复调用 PQgetResult,直到它返回空指针,表明命令已经完成。(如果当前没有正在执行的命令,调用 PQgetResult 会立即返回空指针。)对于 PQgetResult 返回的每个非空结果,都应使用前文介绍的 PGresult 访问函数处理。使用完毕后,不要忘记调用 PQclear 释放每个结果对象。注意,只有存在正在执行的命令,且所需响应数据尚未被 PQconsumeInput 读取时,PQgetResult 才会阻塞。

Note

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

使用PQsendQueryPQgetResult解决了PQexec的一个问题:如果一个命令字符串包含多个SQL命令,这些命令的结果可以被个别地获得(顺便说一句:这样就允许一种简单的重叠处理形式, 客户端可以处理一个命令的结果,而同时服务器可以继续处理同一命令字符串中后面的查询)。

可以被PQsendQueryPQgetResult获得的另一种常常想要的特性是一次从大型结果中检索一行。这会在Section 34.5中讨论。

仅仅调用PQgetResult仍会使客户端阻塞,直到服务器完成下一条SQL命令。可以通过正确使用另外两个函数来避免这种情况:

PQconsumeInput #

如果有来自服务器的输入可用,则使用之。

int PQconsumeInput(PGconn *conn);

PQconsumeInput通常返回 1 表明没有错误,而返回 0 表明有某种麻烦发生(此时可以用PQerrorMessage)。 注意该结果并不表明是否真正收集了任何输入数据。在调用PQconsumeInput之后,应用可以检查PQisBusy和/或PQnotifies来看看它们的状态是否改变。

即使应用还不准备处理一个结果或通知,PQconsumeInput也可以被调用。 这个函数将读取可用的数据并且把它保存在一个缓冲区中,从而导致一个select()的读准备好指示消失。 因此应用可以使用PQconsumeInput立即清除select()条件,并且在空闲时再检查结果。

PQisBusy #

如果命令仍在忙碌,则返回 1,意味着PQgetResult会阻塞等待输入。返回 0 则表示可以调用PQgetResult,并保证不会阻塞。

int PQisBusy(PGconn *conn);

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

一个使用这些函数的典型应用将有一个主循环,在主循环中会使用select()poll()等待所有它必须响应的情况。 其中之一将是来自服务器的输入可用,对select()来说意味着PQsocket标识的文件描述符上有可读的数据。 当主循环检测到输入准备好时,它将调用PQconsumeInput读取输入。 然后它可以调用PQisBusy,如果PQisBusy返回假(0)则接着调用PQgetResult。 它还可以调用PQnotifies检测NOTIFY消息(见Section 34.8)。

一个使用PQsendQuery/PQgetResult的客户端也可以尝试取消一个正在被服务器处理的命令,见Section 34.6。 但是,不管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,应等待套接字变成读准备好并且接着按照上文所述读取响应。