libpq-async.zh.md 13.8 KB
Newer Older
李少辉-开发者's avatar
李少辉-开发者 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
## 34.4.异步命令处理

[](<>)

这个[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)该函数足以在正常的同步应用程序中提交命令。然而,它有一些对某些用户很重要的缺陷:

-   [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)等待命令完成。应用程序可能还有其他工作要做(比如维护用户界面),在这种情况下,它不想阻止等待响应。

-   由于客户端应用程序在等待结果时暂停执行,因此应用程序很难决定是否要尝试取消正在执行的命令。(这可以通过信号处理器完成,但不能通过其他方式完成。)

-   [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)只能返回一个`PGresult`结构如果提交的命令字符串包含多个SQL命令,则除最后一个命令外`PGresult`被丢弃[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC).

-   [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)始终收集命令的整个结果,并将其缓冲在单个`PGresult`。虽然这简化了应用程序的错误处理逻辑,但对于包含多行的结果来说可能不切实际。

    不喜欢这些限制的应用程序可以使用[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)它由以下部分构成:[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)和[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT).还有[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),[`PQsendPrepare`](libpq-async.html#LIBPQ-PQSENDPREPARE),[`PQsendQueryPrepared`](libpq-async.html#LIBPQ-PQSENDQUERYPREPARED),[`PQsendDescribePrepared`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPREPARED), 和[`PQsendDescribePortal`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPORTAL), 它可以与[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)复制的功能[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS),[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE),[`PQexec 准备`](libpq-exec.html#LIBPQ-PQEXECPREPARED),[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED), 和[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL)分别。

`PQsendQuery`[](<>)

向服务器提交命令而不等待结果。如果命令成功发送则返回 1,否则返回 0(在这种情况下,使用[`PQerrorMessage`](libpq-status.html#LIBPQ-PQERRORMESSAGE)以获取有关故障的更多信息)。

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

调用成功后[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY), 称呼[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)一次或多次获得结果。[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)不能再次调用(在同一连接上),直到[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)返回了一个空指针,表示命令执行完毕。

在管道模式下,不允许包含多个 SQL 命令的命令字符串。

`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`](libpq-async.html#LIBPQ-PQSENDQUERY)除了查询参数可以与查询字符串分开指定。该函数的参数的处理方式与[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS).喜欢[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS),它只允许查询字符串中有一个命令。

`PQsendPrepare`[](<>)

发送请求以使用给定参数创建准备好的语句,而无需等待完成。

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

这是一个异步版本[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)判断服务器是否成功创建了prepared statement。该函数的参数的处理方式与[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE).

`PQsendQueryPrepared`[](<>)

发送请求以执行具有给定参数的准备好的语句,而无需等待结果。

```
int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);
```

这类似于[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),但要执行的命令是通过命名先前准备的语句来指定的,而不是给出查询字符串。该函数的参数的处理方式与[`PQexec 准备`](libpq-exec.html#LIBPQ-PQEXECPREPARED).

`PQsendDescribePrepared`[](<>)

提交请求以获取有关指定预准备语句的信息,而无需等待完成。

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

这是一个异步版本[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)来获得结果。该函数的参数的处理方式与[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED).

`PQsendDescribePortal`[](<>)

提交请求以获取有关指定门户的信息,而无需等待完成。

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

这是一个异步版本[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)来获得结果。该函数的参数的处理方式与[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL).

`PQgetResult`[](<>)

等待前一个结果的下一个结果[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY),[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),[`PQsendPrepare`](libpq-async.html#LIBPQ-PQSENDPREPARE),[`PQsendQueryPrepared`](libpq-async.html#LIBPQ-PQSENDQUERYPREPARED),[`PQsendDescribePrepared`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPREPARED),[`PQsendDescribePortal`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPORTAL), 或者[`PQpipelineSync`](libpq-pipeline-mode.html#LIBPQ-PQPIPELINESYNC)调用,并返回它。命令完成时返回一个空指针,不会再有结果。

```
PGresult *PQgetResult(PGconn *conn);
```

[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)必须重复调用,直到它返回一个空指针,表示命令完成。(如果在没有命令处于活动状态时调用,[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)将立即返回一个空指针。)每个非空结果来自[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)应该使用相同的处理`PG结果`前面描述的访问器函数。不要忘记释放每个结果对象[`PQclear`](libpq-exec.html#LIBPQ-PQCLEAR)完成后。注意[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)仅当命令处于活动状态且必要的响应数据尚未被读取时才会阻塞[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT).

在流水线模式下,`PQgetResult`除非发生错误,否则将正常返回;对于在导致错误的查询之后发送的任何后续查询,直到(并且不包括)下一个同步点,类型的特殊结果`PGRES_PIPELINE_ABORTED`将被返回,并在其后返回一个空指针。当达到管道同步点时,类型为`PGRES_PIPELINE_SYNC`将被退回。紧跟在同步点之后的下一个查询的结果(即同步点之后不返回空指针)。

### 笔记

即使当[`PQresult状态`](libpq-exec.html#LIBPQ-PQRESULTSTATUS)表示致命错误,[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)应该调用直到它返回一个空指针,以允许 libpq 完全处理错误信息。

使用[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)解决其中之一[`执行程序`](libpq-exec.html#LIBPQ-PQEXEC)的问题:如果一个命令字符串包含多个SQL命令,则可以单独获取这些命令的结果。(顺便说一下,这允许一种简单形式的重叠处理:客户端可以处理一个命令的结果,而服务器仍在处理同一命令字符串中的后续查询。)

另一个经常需要的功能,可以通过[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)一次检索大查询结果一行。这在[第 34.6 节](libpq-single-row-mode.html).

本身,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)仍然会导致客户端阻塞,直到服务器完成下一个 SQL 命令。这可以通过正确使用另外两个功能来避免:

`PQconsume输入`[](<>)

如果可以从服务器获得输入,则使用它。

```
int PQconsumeInput(PGconn *conn);
```

[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)通常返回 1 表示“没有错误”,但如果出现某种故障则返回 0(在这种情况下[`PQerrorMessage`](libpq-status.html#LIBPQ-PQERRORMESSAGE)可以咨询)。请注意,结果并未说明是否实际收集了任何输入数据。打电话后[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),应用程序可以检查[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)和/或`PQ 通知`看看他们的状态是否发生了变化。

[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)即使应用程序还没有准备好处理结果或通知,也可以调用。该函数将读取可用数据并将其保存在缓冲区中,从而导致`选择()`阅读就绪指示离开。该应用程序因此可以使用[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)清除`选择()`立即状况,然后在闲暇时检查结果。

`PQis忙碌`[](<>)

如果命令忙,则返回 1,即[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)会阻塞等待输入。返回 0 表示[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)可以在保证不阻塞的情况下调用。

```
int PQisBusy(PGconn *conn);
```

[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)本身不会尝试从服务器读取数据;所以[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)必须先调用,否则忙碌状态永远不会结束。

使用这些函数的典型应用程序将有一个主循环,它使用`选择()`要么`轮询()`等待它必须响应的所有条件。条件之一将从服务器输入,根据`选择()`表示文件描述符上的可读数据[`PQsocket`](libpq-status.html#LIBPQ-PQSOCKET).当主循环检测到输入就绪时,它应该调用[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)读取输入。然后它可以调用[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY), 其次是[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)如果[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)返回假 (0)。它也可以调用`PQ 通知`检测`通知`消息(见[第 34.9 节](libpq-notify.html))。

使用的客户端[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)/[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)还可以尝试取消服务器仍在处理的命令;看[第 34.7 节](libpq-cancel.html).但不管[`取消`](libpq-cancel.html#LIBPQ-PQCANCEL),应用程序必须使用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)。成功取消只会导致命令提前终止。

通过使用上述功能,可以避免在等待来自数据库服务器的输入时发生阻塞。但是,应用程序仍有可能阻止等待向服务器发送输出。这种情况相对少见,但如果发送很长的SQL命令或数据值,就会发生这种情况。(如果应用程序通过`抄送`(然而)为了防止这种可能性并实现完全无阻塞的数据库操作,可以使用以下附加功能。

`PQsetnonblocking`[](<>)

设置连接的非阻塞状态。

```
int PQsetnonblocking(PGconn *conn, int arg);
```

如果需要,将连接状态设置为非阻塞*`阿格`*是1,如果*`阿格`*是0。如果正常,则返回0;如果错误,则返回1。

在非阻塞状态下,调用[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY),[`PQputline`](libpq-copy.html#LIBPQ-PQPUTLINE),[`PQputnbytes`](libpq-copy.html#LIBPQ-PQPUTNBYTES),[`PQputCopyData`](libpq-copy.html#LIBPQ-PQPUTCOPYDATA)[`PQendcopy`](libpq-copy.html#LIBPQ-PQENDCOPY)如果需要再次调用,则不会阻止,而是返回错误。

注意[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)不支持非阻塞模式;如果它被调用,它将以阻塞方式运行。

`PQIS非阻塞`[](<>)

返回数据库连接的阻塞状态。

```
int PQisnonblocking(const PGconn *conn);
```

如果连接设置为非阻塞模式,则返回1;如果连接设置为阻塞模式,则返回0。

`PQflush`[](<>)

尝试将任何排队的输出数据刷新到服务器。如果成功(或如果发送队列为空),则返回0;如果由于某种原因失败,则返回-1;如果无法发送发送队列中的所有数据,则返回1(这种情况仅在连接未阻塞时发生)。

```
int PQflush(PGconn *conn);
```

在非阻塞连接上发送任何命令或数据后,调用[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH).如果返回1,则等待套接字变为读写就绪。如果已准备好写入,请致电[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)再一次如果它已准备就绪,请致电[`pqconsumer输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),然后打电话[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)再一次重复直到[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)返回0。(有必要检查read ready(读取准备就绪)并使用[`pqconsumer输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),因为服务器可以阻止向我们发送数据,例如通知消息,并且在我们读取数据之前不会读取我们的数据。)一旦[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)返回0,等待套接字读取就绪,然后如上所述读取响应。