1.3. 异步查询处理 #
PQexec 函数足以在简单的同步应用中提交查询。但它有几个主要缺陷:
PQexec会等待查询完成。应用可能有其他工作要做(例如维护一个用户界面),这种情况下它不会想要阻塞等待响应。由于控制权埋在
PQexec内部,前端很难在需要时决定尝试取消正在进行的查询。(这可以在信号处理器中完成,但别无他法。)PQexec只能返回一个 PGresult 结构。如果提交的查询串包含多条 SQL 命令,除了最后一个 PGresult 之外都会被PQexec丢弃。
不喜欢这些限制的应用可以改用构成
PQexec 的底层函数:PQsendQuery 和 PQgetResult。
使用此功能的旧程序以及
PQputline 和 PQputnbytes
都可能在等待向后端发送数据时阻塞。为了解决这一问题,加入了
PQsetnonblocking 函数。
旧应用可以不使用
PQsetnonblocking
而保持旧的、可能阻塞的行为。新程序则可以使用
PQsetnonblocking 实现到后端的完全非阻塞连接。
PQsetnonblocking设置连接的非阻塞状态。int PQsetnonblocking(PGconn *conn, int arg)
arg 为 TRUE 时把连接置为非阻塞状态,arg 为 FALSE 时置为阻塞状态。成功返回 0,出错返回 -1。
在非阻塞状态下,对
PQputline、PQputnbytes、PQsendQuery和PQendcopy的调用不会阻塞;如果需要再次调用,它们会返回错误。当数据库连接被设置为非阻塞模式后又调用了
PQexec时,它会临时把连接置为阻塞状态,直到该PQexec完成。预计不久的将来,libpq 的更多部分将变得对
PQsetnonblocking功能安全。PQisnonblocking返回数据库连接的阻塞状态。int PQisnonblocking(const PGconn *conn)
连接设置为非阻塞模式时返回 TRUE,阻塞时返回 FALSE。
PQsendQuery向 Postgres 提交一个查询而不等待结果。查询成功发出返回 TRUE,否则返回 FALSE(此时用 PQerrorMessage 获取失败的更多信息)。int PQsendQuery(PGconn *conn, const char *query);成功调用
PQsendQuery后,调用一次或多次PQgetResult获取查询结果。在PQgetResult返回 NULL(表示查询已完成)之前,不得(在同一连接上)再次调用PQsendQuery。PQgetResult等待先前一次PQsendQuery的下一个结果并将其返回。查询完成且不再有结果时返回 NULL。PGresult *PQgetResult(PGconn *conn);
必须反复调用
PQgetResult直到它返回 NULL,表示查询已完成。(如果在没有活跃查询时调用,PQgetResult会立即返回 NULL。)对PQgetResult返回的每个非空结果,应使用前文所述的同一批 PGresult 访问函数处理。用完每个结果对象后别忘了用PQclear释放。注意,只有当查询处于活跃状态且必要的响应数据尚未被PQconsumeInput读取时,PQgetResult才会阻塞。
使用 PQsendQuery 和 PQgetResult
解决了 PQexec 的一个问题:如果查询字符串包含多条
SQL 命令,这些命令的结果可以分别获取。(顺便说一句,这也支持一种简单的重叠处理:前端可以处理某条查询的结果,同时后端继续处理同一查询串中后面的查询。)但调用
PQgetResult 仍会使前端阻塞,直到后端完成下一条
SQL 命令。恰当使用另外三个函数可以避免这一点:
PQconsumeInput如果后端有可用的输入,消费它。int PQconsumeInput(PGconn *conn);
PQconsumeInput正常返回 1 表示"no error"(没有错误);发生问题时返回 0(此时PQerrorMessage会被设置)。注意,返回结果并不说明是否实际读取了输入数据。调用PQconsumeInput后,应用可以检查PQisBusy和/或PQnotifies,看它们的状态是否发生了变化。即使应用尚未准备好处理结果或通知,也可以调用
PQconsumeInput。该例程会读取可用数据并保存到缓冲区中,从而使select(2) 的可读就绪指示消失。因此应用可以用PQconsumeInput立即清除select的就绪条件,随后再从容地检查结果。PQisBusy如果查询仍在忙碌则返回 1,也就是说PQgetResult会阻塞等待输入。返回 0 表示可以放心调用PQgetResult而不会阻塞。int PQisBusy(PGconn *conn);
PQisBusy本身不会尝试从后端读取数据;因此必须先调用PQconsumeInput,否则忙碌状态永远不会结束。PQflush尝试刷新排队发往后端的任何数据,成功(或发送队列为空)返回 0,因某种原因失败返回 EOF。int PQflush(PGconn *conn);
在非阻塞连接上,调用
select判断响应是否到达之前需要先调用PQflush。返回 0 可确保没有已排队但尚未真正发送到后端的数据。只有使用了PQsetnonblocking的应用才需要这样做。PQsocket获取后端连接套接字的文件描述符号。有效的描述符将 >= 0;结果为 -1 表示当前没有打开的后端连接。int PQsocket(const PGconn *conn);
应当用
PQsocket获取后端套接字描述符,为执行select(2) 做准备。这使使用阻塞连接的应用可以同时等待后端响应或其他条件。如果select(2) 的结果表明可以从后端套接字读取数据,就应调用PQconsumeInput读取数据;随后即可用PQisBusy、PQgetResult和/或PQnotifies处理响应。非阻塞连接(使用了
PQsetnonblocking的连接)在PQflush返回 0、表明没有缓冲数据等待发往后端之前,不应使用select。
使用这些函数的典型前端会有一个主循环,用
select(2)
等待它必须响应的所有条件。其中一个条件是后端有可读的输入;用
select 的话说,就是由
PQsocket
标识的文件描述符上有可读数据。主循环检测到输入就绪时,应调用
PQconsumeInput
读取输入。然后可以调用
PQisBusy,若
PQisBusy 返回假(0),接着调用
PQgetResult。还可以调用
PQnotifies 检测 NOTIFY 消息(见下面的"异步通知")。
使用 PQsendQuery/PQgetResult
的前端还可以尝试取消一个仍随后端处理中的查询。
PQrequestCancel请求 Postgres 放弃对当前查询的处理。int PQrequestCancel(PGconn *conn);
取消请求成功发出则返回 1,否则返回 0。(若为 0,
PQerrorMessage会说明原因。)但成功发出并不能保证请求会生效。无论PQrequestCancel的返回值如何,应用都必须继续用PQgetResult按正常顺序读取结果。如果取消生效,当前查询会提前终止并返回一个错误结果。如果取消失败(例如因为后端已完成该查询的处理),则根本不会有可见的结果。
注意,如果当前查询是事务的一部分,取消将中止整个事务。
PQrequestCancel 可以在信号处理器中安全地调用。因此,如果取消的决定可以在信号处理器中做出,也可以将它与普通的
PQexec 配合使用。例如,psql
从 SIGINT 信号处理器中调用
PQrequestCancel,从而允许交互式地取消它通过
PQexec 发出的查询。注意,如果连接当前未打开或后端当前没有在处理查询,PQrequestCancel
将不起作用。