1.4. 异步查询处理
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, 把联接状态设置为阻塞.如果 OK 返回 0,如果错误返回 -1. 此函数将确保对
在非阻塞状态,调用 PQputline,PQputnbytes, PQsendQuery 和 PQendcopy 的时候不被阻塞, 而是如果需要再次它们时将是返回一个错误(而不是阻塞)。
当把一个数据库的联接设置为非阻塞的模式并且调用了 PQexec,它将暂时把联接状态设置为阻塞模式直到 PQexec 完成。
在不久的将来将有更多的libpq会设计成在 PQsetnonblocking 方式下是安全的。
*
PQisnonblocking 返回数据库联接的阻塞状态。
int PQisnonblocking(const PGconn *conn)
如果联接设置为非阻塞状态,返回 1,如果是阻塞状态返回 0。
*
PQsendQuery 向服务器提交一个命令而不等待结果. 如果查询成功发送则返回 1,否则返回 0.此时,可以用PQerrorMessage 获取关于失败的信息).
int PQsendQuery(PGconn *conn,
const char *query);
在成功调用 PQsendQuery后,调用 PQgetResult 一次或者多次获取结果. 可以不再调用PQsendQuery (在同一次联接里) 直到 PQgetResult 返回 NULL,表明命令完成.
*
PQgetResult 等待从前面 PQsendQuery 调用返回的下一个结果, 然后返回之.当查询结束并且没有更多结果后返回 NULL.
PGresult *PQgetResult(PGconn *conn);
必须重复的调用 PQgetResult ,直到它返回 NULL, 表明该命令结束.(如果在没有活跃的命令时调用, PQgetResult 将只是立即返回 NULL.) 每个 PQgetResult 返回的非 NULL 结果都应该用前面 描述的 PGresult 访问函数进行分析. 不要忘了在结束分析后用 PQclear 释放每个结果对象. 注意, PQgetResult 只是在有查询激活而且必须的返回数据还没有被 PQconsumeInput 读取时阻塞.
使用 PQsendQuery 和 PQgetResult 解决了 PQexec的一个问题:如果一个命令字符串包含多个 SQL 命令,这些命令的结果可以独立的获得.(顺便说一句:这样就允许一种简单的重叠处理模式,前端可以处理一个查询的结果而后端可以仍然在处理同一命令字符串的后面的查询.)但是,调用 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 ,否则忙状态将永远不会消除.
*
PQflush 试图把任何正在排队的数据冲刷到后端,如果成功(或者发送队列为空)返回 0,如果因某种原因失败返回EOF。
int PQflush(PGconn *conn);
在一个非阻塞的联接调用 select() 判断是否有响应到达之前需要调用一个 PQflush 。如果返回 0 则保证了与后端的发送队列里面没有待发送的数据。只有使用了 PQsetnonblocking 的应用需要这个。
*
PQsocket 获取用于后端联接套接字的文件描述符号. 一个有效的描述符应该是 >= 0;一个 -1 表明当前没有打开与后端的联接.
int PQsocket(const PGconn *conn);
PQsocket 应该用于获取准备调用 select() 的后端套接字描述符.这就允许一个应用使用阻塞的联接等待后端的响应或者其他条件.如果 select() 的结果表明可以从后端套接字读取数据,那么应该调用 PQconsumeInput 读取数据;之后,,PQisBusy,PQgetResult,和/或 PQnotifies 可用于处理返回信息.
非阻塞的联接(那些使用了 PQsetnonblocking的联接)在 PQflush 返回 0 之前,(这表明没有数据缓冲着等待发送给后端)不应该使用 select()。
一个使用这些函数的典型的前端将有一个主循环使用 select() 等待所有它必须处理的条件.其中一个条件将会是后端来的数据已准备好,从 select() 的角度来看就是 PQsocket 标识的文件描述符上已经有可读取的数据.当主循环侦测到输入准备好,它将调用PQconsumeInput 读取输入.然后可以调用 PQisBusy 返回 false (0)后面可以跟着 PQgetResult。同样它(用户应用)可以调用 PQnotifies测 NOTIFY 信息(参阅下面的 "异步通知").例子程序节里面给出了一个例子程序.
一个使用 PQsendQuery/PQgetResult 的前端同样也可以试图取消一个正在被后端处理的命令.
*
PQrequestCancel 要求 PostgreSQL 放弃处理当前命令.
int PQrequestCancel(PGconn *conn);
如果取消的请求成功发送,返回值是 1,否则是 0.(如果为假,PQerrorMessage 会告之为什么.)不过,取消请求的成功发送将不保证请求将产生作用.不管 PQrequestCancel 的返回值是什么,应用都必须继续使用 PQgetResult进行通常的后续的结果读取工作.如果取消动作生效,当前的命令将提前退出并返回一个错误结果.如果取消动作失败(也就是后端已经处理完命令了),那么将没有可见的结果.
注意:如果当前的命令是事务的一部分,取消动作将退出整个事务.
PQrequestCancel 可以很安全地从一个信号句柄里调用.所以,如果取消动作可以从信号句柄里发出的话,它也可以与简单的 PQexec一起使用.例如, psql 从一个SIGINT信号句柄里调用 PQrequestCancel ,因此允许交互地取消通过 PQexec 发出的查询.注意,如果没有与后端建立联接或者后端当前没有处理命令, PQrequestCancel 将不发生做用.