On this page
33.4. 异步命令处理
PQexec
功能足以在正常的同步应用程序中提交命令。但是,它有一些缺陷,对于某些用户来说可能很重要:
PQexec
await 命令完成。该应用程序可能还有其他工作要做(例如维护用户界面),在这种情况下,它不想阻塞 await 响应。由于 Client 端应用程序在 await 结果时会暂停执行,因此应用程序很难决定要取消正在执行的命令。 (可以通过 signal 处理程序来完成,但是不能这样做.)
PQexec
只能返回一个PGresult
结构。如果提交的命令字符串包含多个 SQL 命令,则除最后一个PGresult
外的所有其他命令均被PQexec
丢弃。PQexec
始终收集命令的整个结果,并将其缓冲在单个PGresult
中。尽管这简化了应用程序的错误处理逻辑,但对于包含许多行的结果来说可能是不切实际的。
不喜欢这些限制的应用程序可以改用PQexec
的基础功能:PQsendQuery
和PQgetResult
。还有PQsendQueryParams
,PQsendPrepare
,PQsendQueryPrepared
,PQsendDescribePrepared
和PQsendDescribePortal
,可与PQgetResult
一起使用,以分别复制PQexecParams
,PQprepare
,PQexecPrepared
,PQdescribePrepared
和PQdescribePortal
的功能。
int PQsendQuery(PGconn *conn, const char *command);
成功致电PQsendQuery
后,致电PQgetResult
一次或多次以获取结果。 PQgetResult
返回空指针(指示该命令已完成)之前,不能再次调用PQsendQuery
(在同一连接上)。
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 协议的连接,并且在查询字符串中仅允许一个命令。
int PQsendPrepare(PGconn *conn,
const char *stmtName,
const char *query,
int nParams,
const Oid *paramTypes);
这是PQprepare
的异步版本:如果能够分派请求,则返回 1,否则返回 0.成功调用后,调用PQgetResult
确定服务器是否成功创建了准备好的语句。函数的参数处理与PQprepare
相同。像PQprepare
一样,它不适用于 2.0 协议的连接。
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 协议的连接。
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
这是PQdescribePrepared
的异步版本:如果能够分派请求,则返回 1,否则返回 0.通话成功后,致电PQgetResult
以获取结果。函数的参数处理与PQdescribePrepared
相同。像PQdescribePrepared
一样,它不适用于 2.0 协议的连接。
int PQsendDescribePortal(PGconn *conn, const char *portalName);
这是PQdescribePortal
的异步版本:如果能够分派请求,则返回 1,否则返回 0.通话成功后,致电PQgetResult
以获取结果。函数的参数处理与PQdescribePortal
相同。像PQdescribePortal
一样,它不适用于 2.0 协议的连接。
PQgetResult
- await 先前的
PQsendQuery
,PQsendQueryParams
,PQsendPrepare
,PQsendQueryPrepared
,PQsendDescribePrepared
或PQsendDescribePortal
调用的下一个结果,并将其返回。命令完成后,将返回空指针,并且将不再有结果。
- await 先前的
PGresult *PQgetResult(PGconn *conn);
PQgetResult
必须重复调用,直到返回空指针,表明命令已完成。 (如果在未激活任何命令的情况下调用PQgetResult
,则将立即返回一个空指针.)PQgetResult
的每个非空结果都应使用前面所述的相同PGresult
访问器函数进行处理。完成后,不要忘记用PQclear
释放每个结果对象。注意,仅当命令处于活动状态并且PQconsumeInput
尚未读取必要的响应数据时,PQgetResult
才会阻塞。
Note
即使PQresultStatus
表示致命错误,也应调用PQgetResult
直到返回空指针,以允许 libpq 完全处理错误信息。
使用PQsendQuery
和PQgetResult
解决了PQexec
的问题之一:如果命令字符串包含多个 SQL 命令,则可以分别获取这些命令的结果。 (顺便说一下,这允许一种简单形式的重叠处理:当服务器仍在使用相同的命令字符串处理以后的查询时,Client 端可以处理一个命令的结果.)
可以通过PQsendQuery
和PQgetResult
获得的另一个经常需要的功能是一次连续检索大型查询结果。 Section 33.5中对此进行了讨论。
就其本身而言,调用PQgetResult
仍将导致 Client 端阻塞,直到服务器完成下一个 SQL 命令为止。可以通过另外使用两个功能来避免这种情况:
int PQconsumeInput(PGconn *conn);
PQconsumeInput
通常返回 1 表示“无错误”,但如果遇到某种麻烦则返回 0(在这种情况下,可以咨询PQerrorMessage
)。请注意,结果并未说明是否实际收集了任何 Importing 数据。调用PQconsumeInput
之后,应用程序可以检查PQisBusy
和/或PQnotifies
以查看其状态是否已更改。
即使应用程序尚未准备好处理结果或通知,也可以调用PQconsumeInput
。该函数将读取可用数据并将其保存在缓冲区中,从而使select()
就绪指示消失。因此,应用程序可以使用PQconsumeInput
立即清除select()
条件,然后休闲地检查结果。
int PQisBusy(PGconn *conn);
PQisBusy
本身不会尝试从服务器读取数据;因此必须先调用PQconsumeInput
,否则繁忙状态将永远不会结束。
使用这些功能的典型应用程序将具有一个主循环,该主循环使用select()
或poll()
await 必须响应的所有条件。条件之一将从服务器 Importing,可用select()
表示由PQsocket
标识的文件 Descriptors 上的可读数据。当主循环检测到 Importing 准备就绪时,应调用PQconsumeInput
读取 Importing。然后,它可以调用PQisBusy
,然后调用PQgetResult
,如果PQisBusy
返回 false(0)。它还可以调用PQnotifies
来检测NOTIFY
条消息(请参阅Section 33.8)。
使用PQsendQuery
/PQgetResult
的 Client 端也可以尝试取消服务器仍在处理的命令。参见Section 33.6。但是无论返回值PQcancel
如何,应用程序都必须 continue 使用PQgetResult
进行正常的结果读取序列。成功的取消只会导致命令比其他情况早终止。
通过使用上述功能,可以避免在 await 数据库服务器的 Importing 时发生阻塞。但是,该应用程序仍然有可能阻止 await 将输出发送到服务器的 await。这相对不常见,但是如果发送了很长的 SQL 命令或数据值,则可能会发生这种情况。 (但是,如果应用程序通过COPY IN
发送数据,则更有可能.)为了防止这种可能性并实现完全无阻塞的数据库操作,可以使用以下附加功能。
int PQsetnonblocking(PGconn *conn, int arg);
如果* arg
为 1,则将连接状态设置为非阻塞;如果 arg
*为 0,则将连接状态设置为阻塞。如果 OK,则返回 0,如果错误,则返回-1.
在非阻塞状态下,对PQsendQuery
,PQputline
,PQputnbytes
,PQputCopyData
和PQendcopy
的调用不会阻塞,而是在需要再次调用时返回错误。
请注意,PQexec
不支持非阻塞模式;如果它被调用,它将以阻塞的方式起作用。
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 套接字准备就绪,然后如上所述读取响应。