34.4. 异步命令处理 #
PQexec 函数对于在普通的同步应用中提交命令是足以胜任的。不过,它的一些缺点可能对某些用户很重要:
如果应用程序不希望受到这些限制,可以改用构成 PQexec 的底层函数:PQsendQuery 和 PQgetResult。此外,PQsendQueryParams、PQsendPrepare、PQsendQueryPrepared、PQsendDescribePrepared、PQsendDescribePortal 可以与 PQgetResult 配合使用,分别实现 PQexecParams、PQprepare、PQexecPrepared、PQdescribePrepared、PQdescribePortal 的功能。
PQsendQuery#向服务器提交命令,不等待结果。命令发送成功时返回 1,否则返回 0(此时可使用
PQerrorMessage获取更多失败信息)。int PQsendQuery(PGconn *conn, const char *command);
成功调用
PQsendQuery后,应调用PQgetResult一次或多次来获取结果。在PQgetResult返回空指针、表明命令已完成之前,不得在同一连接上再次调用PQsendQuery。在管道模式下,此函数被禁止使用。
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一样,查询字符串中只允许包含一条命令。PQsendPrepare#发送按给定参数创建预备语句的请求,不等待完成。
int PQsendPrepare(PGconn *conn, const char *stmtName, const char *query, int nParams, const Oid *paramTypes);这是
PQprepare的异步版本:请求发送成功时返回 1,否则返回 0。调用成功后,再调用PQgetResult,确定服务器是否成功创建了预备语句。函数参数的处理方式与PQprepare相同。PQsendQueryPrepared#发送使用给定参数执行预备语句的请求,不等待结果。
int PQsendQueryPrepared(PGconn *conn, const char *stmtName, int nParams, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat);该函数类似于
PQsendQueryParams,但通过已创建的预备语句的名称指定要执行的命令,而非提供查询字符串。函数参数的处理方式与PQexecPrepared相同。PQsendDescribePrepared#提交请求以获取关于指定预备语句的信息,而无需等待完成。
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
这是
PQdescribePrepared的异步版本:如果能够发送请求,则返回 1,否则返回 0。成功调用后,调用PQgetResult来获取结果。函数的参数处理方式与PQdescribePrepared相同。PQsendDescribePortal#提交请求以获取有关指定 portal 的信息,而无需等待完成。
int PQsendDescribePortal(PGconn *conn, const char *portalName);
这是
PQdescribePortal的异步版本:如果能够发送请求,则返回 1,否则返回 0。成功调用后,调用PQgetResult以获取结果。该函数的参数处理方式与PQdescribePortal相同。PQgetResult#等待来自先前的
PQsendQuery、PQsendQueryParams、PQsendPrepare、PQsendQueryPrepared、PQsendDescribePrepared、PQsendDescribePortal或PQpipelineSync调用的下一个结果,并返回它。当命令完成且不会再有更多结果时,将返回空指针。PGresult *PQgetResult(PGconn *conn);
必须反复调用
PQgetResult,直到它返回空指针,表明命令已经完成。(如果当前没有正在执行的命令,调用PQgetResult会立即返回空指针。)对于PQgetResult返回的非空指针,应使用前文介绍的PGresult访问函数处理相应结果。使用完毕后,不要忘记调用PQclear释放每个结果对象。注意,只有存在正在执行的命令,且所需响应数据尚未被PQconsumeInput读取时,PQgetResult才会阻塞。在管道模式下,
PQgetResult将正常返回,除非发生错误;对于在导致错误的查询之后发送的任何后续查询,直到(但不包括)下一个同步点,将返回特殊类型的结果PGRES_PIPELINE_ABORTED,并在其后返回空指针。当达到管道同步点时,将返回类型为PGRES_PIPELINE_SYNC的结果。在同步点之后立即跟随下一个查询的结果(即,在同步点之后不会返回空指针)。注意
即使
PQresultStatus指示发生了致命错误,也应该调用PQgetResult直到它返回一个空指针,以便 libpq 完全处理错误信息。
使用 PQsendQuery 和 PQgetResult 可以解决 PQexec 的一个问题:如果命令字符串包含多个 SQL 命令,就能分别获取这些命令的结果。(这也支持一种简单的重叠处理方式:客户端可以处理某条命令的结果,同时服务器继续处理同一命令字符串中后面的查询。)
使用 PQsendQuery 和 PQgetResult 还可以实现另一项常见需求:从大型查询结果中一次读取一行。详见第 34.6 节。
仅仅调用 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 消息(见第 34.9 节)。
一个使用 PQsendQuery/PQgetResult 的客户端也可以尝试取消一个正在被服务器处理的命令,见第 34.7 节。但是,不管 PQcancel 的返回值是什么,应用都必须继续使用 PQgetResult 进行正常的结果读取序列。一次成功的取消只会导致命令比不取消时更快终止。
使用上述函数可以避免在等待数据库服务器输入时阻塞。不过,应用程序仍可能在等待向服务器发送输出时阻塞。这种情况较少见,但发送很长的 SQL 命令或数据值时可能发生。(如果应用程序通过 COPY IN 发送数据,发生的可能性则大得多。)为了防止这种情况,实现完全非阻塞的数据库操作,可以使用以下附加函数。
PQsetnonblocking#设置连接的非阻塞状态。
int PQsetnonblocking(PGconn *conn, int arg);
如果
arg为 1,则将连接状态设置为非阻塞,如果arg为 0,则设置为阻塞。如果成功返回 0,出错返回-1。在非阻塞状态下,成功调用
PQsendQuery、PQputline、PQputnbytes、PQputCopyData和PQendcopy不会阻塞;产生的数据保存在本地输出缓冲区中,等待发送。失败的调用会返回错误,必须重试。请注意,
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 后,等待套接字变为可读,再按前述方法读取响应。
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.