32.5. Конвейерный режим #
Конвейерный режим libpq даёт приложению отправить запрос, не получая результат предыдущего. Используя преимущества конвейерного режима, клиент будет меньше ждать сервер, поскольку несколько запросов/результатов могут быть отправлены/получены в одной сетевой транзакции.
Хотя конвейерный режим обеспечивает значительный прирост производительности, разрабатывать клиентские приложения, использующие этот режим, гораздо сложнее, так как требуется реализовывать очереди ожидающих запросов и сопоставлять результаты с запросами в очереди.
Для конвейерного режима также обычно требуется больше памяти как на стороне клиента, так и на стороне сервера, хотя тщательное и агрессивное управление очередью отправки/получения может это нивелировать. Это не зависит от того, в каком режиме — блокирующем или нет — устанавливается подключение.
Хотя конвейерный API в libpq появился с выходом PostgreSQL 14, это клиентская функциональность, которая не требует специальной поддержки на стороне сервера и работает с любым сервером, поддерживающем 3-ю версию расширенного протокола запросов. За дополнительными сведениями обратитесь к Подразделу 53.2.4.
32.5.1. Использование конвейерного режима #
Для запуска конвейеров приложение должно переключить соединение в конвейерный режим посредством функции PQenterPipelineMode
. Можно проверить, включён ли данный режим, используя функцию PQpipelineStatus
. В конвейерном режиме разрешены только асинхронные операции, использующие расширенный протокол запросов, а строки команд, содержащие несколько SQL-команд, и команда COPY
запрещены. Использовать функции синхронного выполнения команд, такие как PQfn
, PQexec
, PQexecParams
, PQprepare
, PQexecPrepared
, PQdescribePrepared
, PQdescribePortal
, PQclosePrepared
, PQclosePortal
в этом режиме нельзя. Функцию PQsendQuery
применять также нельзя, так как она использует простой протокол запросов. Обработав результаты всех отправленных команд и итоговый результат конвейера, приложение может вернуться в обычный режим, вызвав PQexitPipelineMode
.
Примечание
Конвейерный режим рекомендуется использовать при работе libpq в неблокирующем режиме. В блокирующем режиме возможны взаимоблокировки на уровне клиент-сервер. [15]
32.5.1.1. Отправка запросов #
Перейдя в конвейерный режим, приложение отправляет запросы, вызывая PQsendQueryParams
или родственную ей функцию PQsendQueryPrepared
, работающую с подготовленными запросами. Данные запросы ставятся в очередь на стороне клиента, а затем сбрасываются на сервер; это происходит, когда вызывается PQpipelineSync
, устанавливающая точку синхронизации в конвейере, или когда вызывается PQflush
. В конвейерном режиме также работают функции PQsendPrepare
, PQsendDescribePrepared
, PQsendDescribePortal
, PQsendClosePrepared
и PQsendClosePortal
. Обработка результатов описана ниже.
Сервер выполняет операторы и в порядке их поступления от клиента возвращает результаты. Сервер начнёт выполнять команды в конвейере немедленно, не дожидаясь конца конвейера. Обратите внимание, что результаты буферизуются на стороне сервера; сервер сбрасывает этот буфер, когда функцией PQpipelineSync
или PQsendPipelineSync
устанавливается точка синхронизации или когда вызывается функция PQsendFlushRequest
. Если при выполнении какого-либо оператора возникает ошибка, сервер прерывает текущую транзакцию и не выполняет никакую следующую команду в очереди до следующей точки синхронизации; для каждой такой команды выдаётся результат PGRES_PIPELINE_ABORTED
. (Это справедливо и тогда, когда в конвейере передаются команды, которые могли бы откатить транзакцию.) Обработка запроса возобновляется после точки синхронизации.
Одна операция вполне может зависеть от результатов предыдущей; например, в одном запросе может создаваться таблица, которую будет использовать следующий запрос в том же конвейере. Точно так же приложение может создать именованный подготовленный оператор и выполнить его с последующими операторами в том же конвейере.
32.5.1.2. Обработка результатов #
Чтобы обработать результат одного запроса в конвейере, приложение многократно вызывает PQgetResult
и обрабатывает каждый её результат, пока PQgetResult
не выдаст NULL. Затем может быть получен результат следующего запроса в конвейере, также с помощью PQgetResult
, и весь цикл повторяется. Результаты отдельных запросов приложение обрабатывает обычным образом. После того, как будут выданы результаты всех запросов в конвейере, PQgetResult
выдаёт результат со значением статуса PGRES_PIPELINE_SYNC
.
Клиент может отложить обработку результатов до тех пор, пока весь конвейер не будет отправлен, или чередовать её с отправкой дальнейших запросов в конвейере; см. Подраздел 32.5.1.4.
Функция PQgetResult
работает так же, как и при обычной асинхронной обработке, но может дополнительно выдавать результаты новых типов PGRES_PIPELINE_SYNC
и PGRES_PIPELINE_ABORTED
. PGRES_PIPELINE_SYNC
выдаётся ровно один раз для каждого вызова PQpipelineSync
или PQsendPipelineSync
в соответствующей точке конвейера. PGRES_PIPELINE_ABORTED
выдаётся вместо обычного результата запроса для первой ошибки и всех последующих результатов до следующего PGRES_PIPELINE_SYNC
; см. Подраздел 32.5.1.3.
Функции PQisBusy
, PQconsumeInput
и т. п. работают как обычно при обработке результатов конвейера. В частности, вызов PQisBusy
в середине конвейера возвращает 0, если были обработаны результаты всех выполненных на данный момент запросов.
libpq не предоставляет приложению никакой информации о запросе, обрабатываемом в данный момент (за исключением того, что PQgetResult
возвращает NULL, чтобы указать, что далее выдаются результаты следующего запроса). Приложение должно отслеживать порядок отправки запросов, чтобы связать их с соответствующими результатами. Для этого приложение обычно использует конечный автомат или очередь.
32.5.1.3. Обработка ошибок #
С точки зрения клиента, после того, как PQresultStatus
возвращает PGRES_FATAL_ERROR
, конвейер помечается как нерабочий. PQresultStatus
будет выдавать результат PGRES_PIPELINE_ABORTED
для каждой оставшейся в очереди операции в нерабочем конвейере. Функция PQpipelineSync
или PQsendPipelineSync
выдаёт результат PGRES_PIPELINE_SYNC
, сигнализируя о возвращении конвейера в рабочее состояние и возобновлении нормальной обработки результатов.
Клиент должен обрабатывать результаты, вызывая PQgetResult
во время восстановления после ошибки.
Если в конвейере передавалась неявная транзакция, то операции, которые уже были выполнены, откатываются, а операции, которые были поставлены в очередь после неудачной операции, полностью пропускаются. То же самое происходит, если в конвейере запускается и фиксируется одна явная транзакция (т. е. первый оператор — BEGIN
, а последний — COMMIT
), за исключением того, что сеанс остаётся в состоянии прерванной транзакции в конце конвейера. Если конвейер содержит несколько явных транзакций, все транзакции, зафиксированные до ошибки, остаются зафиксированными, текущая транзакция прерывается, а все последующие операции полностью пропускаются, включая транзакции. Если выполняется точка синхронизации, когда явный блок транзакции находится в прерванном состоянии, следующий конвейер сразу же становится нерабочим, если следующая команда (ROLLBACK
) не переключает его в обычный режим.
Примечание
Клиент не должен рассчитывать на то, что выполненная работа зафиксирована сразу после того, как был отправлен COMMIT
; только получение соответствующего результата даёт такую гарантию. Поскольку ошибки поступают асинхронно, приложение должно уметь возвращаться к моменту последнего подтверждённого зафиксированного изменения и повторно отправлять работу, выполненную после этого момента, если что-то пойдёт не так.
32.5.1.4. Чередование обработки результатов и отправки запросов #
Во избежание взаимоблокировок с большими конвейерами, клиент должен быть построен вокруг неблокирующего цикла событий, реализованного с использованием таких механизмов операционной системы, как select
, poll
, WaitForMultipleObjectEx
и т. д.
Клиентское приложение, как правило, должно поддерживать очередь ещё не отправленной работы и очередь работы, которая уже отправлена, но её результаты ещё не обработаны. Когда сокет доступен для записи, в него следует отправлять очередной объём работы. Когда он доступен для чтения, из него следует прочитать и обработать результаты, сопоставив их со следующей записью в очереди ожидания результатов. Если объём памяти позволяет, результаты следует читать достаточно часто — дожидаться окончания конвейера не требуется. Конвейеры должны быть ограничены логическими единицами работы, обычно (но не обязательно) по одной транзакции на конвейер. Нет необходимости выходить из конвейерного режима и возвращаться в него между конвейерами, так же как не нужно дожидаться завершения одного конвейере, прежде чем передавать работу в другой.
Пример использования select()
и простого конечного автомата для отслеживания отправляемой работы и полученных результатов находится в src/test/modules/libpq_pipeline/libpq_pipeline.c
в дистрибутиве исходного кода PostgreSQL.
32.5.2. Функции, связанные с конвейерным режимом #
PQpipelineStatus
#Возвращает текущее состояние конвейерного режима для подключения libpq.
PGpipelineStatus PQpipelineStatus(const PGconn *conn);
PQpipelineStatus
может выдавать одно из следующих значений:-
PQ_PIPELINE_ON
Подключение libpq находится в конвейерном режиме.
-
PQ_PIPELINE_OFF
Подключение libpq не находится в конвейерном режиме.
-
PQ_PIPELINE_ABORTED
Соединение libpq находится в конвейерном режиме, и при обработке текущего конвейера произошла ошибка. Флаг прерывания сбрасывается, когда
PQgetResult
возвращает результат типаPGRES_PIPELINE_SYNC
.
-
PQenterPipelineMode
#Переводит подключение в конвейерный режим, если оно в данный момент находится в режиме ожидания или уже находится в конвейерном режиме.
int PQenterPipelineMode(PGconn *conn);
Возвращает 1 в случае успеха. Возвращает 0 и ничего не делает, если соединение в настоящий момент не простаивает, т. е. если у него есть готовый результат, или оно ожидает поступления дополнительных данных от сервера и т. д. Эта функция на самом деле ничего не отправляет серверу, а просто изменяет состояние соединения libpq.
PQexitPipelineMode
#Выводит подключение из конвейерного режима, если подключение находится в нём и его очереди пусты.
int PQexitPipelineMode (PGconn * conn);
Возвращает 1 в случае успеха. Не в конвейерном режиме возвращает 1 и не выполняет никаких действий. Если обработка текущего оператора не завершена или
PQgetResult
не была вызвана для сбора результатов всех ранее отправленных запросов, возвращает 0 (в этом случае используйтеPQerrorMessage
, чтобы получить дополнительную информацию о проблеме).PQpipelineSync
#Отмечает точку синхронизации в конвейере, отправляя сообщение синхронизации и очищая буфер отправки. Эта точка служит ограничителем неявной транзакции и точкой восстановления после ошибки; см. Подраздел 32.5.1.3.
int PQpipelineSync(PGconn *conn);
Возвращает 1 в случае успеха. Возвращает 0, если соединение не находится в конвейерном режиме или сообщение синхронизации отправить не удалось.
PQsendPipelineSync
#Отмечает точку синхронизации в конвейере, отправляя сообщение синхронизации, но не очищая буфер отправки. Эта точка служит ограничителем неявной транзакции и точкой восстановления после ошибки; см. Подраздел 32.5.1.3.
int PQsendPipelineSync(PGconn *conn);
Возвращает 1 в случае успеха. Возвращает 0, если соединение не находится в конвейерном режиме или сообщение синхронизации отправить не удалось. Обратите внимание, что сообщение не сохраняется на сервере автоматически. При необходимости используйте функцию
PQflush
.PQsendFlushRequest
#Отправляет серверу команду сбросить его буфер вывода.
int PQsendFlushRequest(PGconn *conn);
Возвращает 1 в случае успеха. Возвращает 0 в случае любой ошибки.
Сервер сбрасывает свой буфер вывода автоматически, когда вызывается
PQpipelineSync
или передаётся любой запрос не в конвейерном режиме; эта функция полезна в конвейерном режиме: она позволяет сбросить серверный буфер, не устанавливая точку синхронизации. Обратите внимание, что сам этот запрос не передаётся серверу автоматически; чтобы передать его немедленно, вызовитеPQflush
.
32.5.3. Когда использовать конвейерный режим #
Как и в случае с асинхронным режимом запросов, при использовании конвейерного режима нет значительных издержек производительности. Использование конвейерного режима увеличивает сложность клиентского приложения и требует дополнительной осторожности во избежание взаимоблокировок клиент-сервер, но может предложить значительное улучшение производительности в обмен на увеличение объёма используемой памяти из-за более длительного выхода из состояния.
Конвейерный режим наиболее полезен, когда сервер находится на большом расстоянии от клиента, т. е. когда сетевая задержка («ping time») велика, а также когда много небольших операций выполняются в быстрой последовательности. Как правило, использование конвейерных команд даёт меньше преимуществ, когда выполнение каждого запроса занимает в несколько раз больше времени, чем передача данных клиент-сервер и обратно. Операция из 100 операторов, выполняемая на сервере за 300 миллисекунд, без конвейеризации займёт 30 секунд из-за одной только сетевой задержки; с конвейеризацией данная операция потратит не более 0,3 секунды на ожидание результатов от сервера.
Используйте конвейерные команды, когда ваше приложение выполняет множество небольших операций INSERT
, UPDATE
и DELETE
, которые нелегко преобразовать в наборы операций или в операцию COPY
.
Конвейерный режим бесполезен, когда информация из одной операции требуется клиенту для выполнения следующей операции. В таких случаях клиенту придётся ввести точку синхронизации и дождаться полного цикла передачи данных клиент-сервер, чтобы получить требуемые результаты. Однако часто можно настроить клиент для обмена необходимой информацией на стороне сервера. Циклы чтения-изменения-записи особенно хорошо подходят для такой настройки; например:
BEGIN; SELECT x FROM mytable WHERE id = 42 FOR UPDATE; -- result: x=2 -- client adds 1 to x: UPDATE mytable SET x = 3 WHERE id = 42; COMMIT;
можно гораздо эффективнее сделать с помощью:
UPDATE mytable SET x = x + 1 WHERE id = 42;
Конвейеризация менее полезна и более сложна, когда один конвейер содержит несколько транзакций (см. Подраздел 32.5.1.3).
[15] Клиент может заблокироваться, пытаясь передать запросы серверу, а сервер заблокируется, пытаясь выдать клиенту результаты уже выполненных запросов. Это возможно, только когда клиент передаёт так много запросов, что заполняет и свой выходной буфер, и входной буфер сервера, и только затем переключается на обработку передаваемых сервером результатов, но предсказать, когда точно это произойдёт, сложно.
32.5. Pipeline Mode #
libpq pipeline mode allows applications to send a query without having to read the result of the previously sent query. Taking advantage of the pipeline mode, a client will wait less for the server, since multiple queries/results can be sent/received in a single network transaction.
While pipeline mode provides a significant performance boost, writing clients using the pipeline mode is more complex because it involves managing a queue of pending queries and finding which result corresponds to which query in the queue.
Pipeline mode also generally consumes more memory on both the client and server, though careful and aggressive management of the send/receive queue can mitigate this. This applies whether or not the connection is in blocking or non-blocking mode.
While libpq's pipeline API was introduced in PostgreSQL 14, it is a client-side feature which doesn't require special server support and works on any server that supports the v3 extended query protocol. For more information see Section 53.2.4.
32.5.1. Using Pipeline Mode #
To issue pipelines, the application must switch the connection into pipeline mode, which is done with PQenterPipelineMode
. PQpipelineStatus
can be used to test whether pipeline mode is active. In pipeline mode, only asynchronous operations that utilize the extended query protocol are permitted, command strings containing multiple SQL commands are disallowed, and so is COPY
. Using synchronous command execution functions such as PQfn
, PQexec
, PQexecParams
, PQprepare
, PQexecPrepared
, PQdescribePrepared
, PQdescribePortal
, PQclosePrepared
, PQclosePortal
, is an error condition. PQsendQuery
is also disallowed, because it uses the simple query protocol. Once all dispatched commands have had their results processed, and the end pipeline result has been consumed, the application may return to non-pipelined mode with PQexitPipelineMode
.
Note
It is best to use pipeline mode with libpq in non-blocking mode. If used in blocking mode it is possible for a client/server deadlock to occur. [15]
32.5.1.1. Issuing Queries #
After entering pipeline mode, the application dispatches requests using PQsendQueryParams
or its prepared-query sibling PQsendQueryPrepared
. These requests are queued on the client-side until flushed to the server; this occurs when PQpipelineSync
is used to establish a synchronization point in the pipeline, or when PQflush
is called. The functions PQsendPrepare
, PQsendDescribePrepared
, PQsendDescribePortal
, PQsendClosePrepared
, and PQsendClosePortal
also work in pipeline mode. Result processing is described below.
The server executes statements, and returns results, in the order the client sends them. The server will begin executing the commands in the pipeline immediately, not waiting for the end of the pipeline. Note that results are buffered on the server side; the server flushes that buffer when a synchronization point is established with either PQpipelineSync
or PQsendPipelineSync
, or when PQsendFlushRequest
is called. If any statement encounters an error, the server aborts the current transaction and does not execute any subsequent command in the queue until the next synchronization point; a PGRES_PIPELINE_ABORTED
result is produced for each such command. (This remains true even if the commands in the pipeline would rollback the transaction.) Query processing resumes after the synchronization point.
It's fine for one operation to depend on the results of a prior one; for example, one query may define a table that the next query in the same pipeline uses. Similarly, an application may create a named prepared statement and execute it with later statements in the same pipeline.
32.5.1.2. Processing Results #
To process the result of one query in a pipeline, the application calls PQgetResult
repeatedly and handles each result until PQgetResult
returns null. The result from the next query in the pipeline may then be retrieved using PQgetResult
again and the cycle repeated. The application handles individual statement results as normal. When the results of all the queries in the pipeline have been returned, PQgetResult
returns a result containing the status value PGRES_PIPELINE_SYNC
The client may choose to defer result processing until the complete pipeline has been sent, or interleave that with sending further queries in the pipeline; see Section 32.5.1.4.
PQgetResult
behaves the same as for normal asynchronous processing except that it may contain the new PGresult
types PGRES_PIPELINE_SYNC
and PGRES_PIPELINE_ABORTED
. PGRES_PIPELINE_SYNC
is reported exactly once for each PQpipelineSync
or PQsendPipelineSync
at the corresponding point in the pipeline. PGRES_PIPELINE_ABORTED
is emitted in place of a normal query result for the first error and all subsequent results until the next PGRES_PIPELINE_SYNC
; see Section 32.5.1.3.
PQisBusy
, PQconsumeInput
, etc operate as normal when processing pipeline results. In particular, a call to PQisBusy
in the middle of a pipeline returns 0 if the results for all the queries issued so far have been consumed.
libpq does not provide any information to the application about the query currently being processed (except that PQgetResult
returns null to indicate that we start returning the results of next query). The application must keep track of the order in which it sent queries, to associate them with their corresponding results. Applications will typically use a state machine or a FIFO queue for this.
32.5.1.3. Error Handling #
From the client's perspective, after PQresultStatus
returns PGRES_FATAL_ERROR
, the pipeline is flagged as aborted. PQresultStatus
will report a PGRES_PIPELINE_ABORTED
result for each remaining queued operation in an aborted pipeline. The result for PQpipelineSync
or PQsendPipelineSync
is reported as PGRES_PIPELINE_SYNC
to signal the end of the aborted pipeline and resumption of normal result processing.
The client must process results with PQgetResult
during error recovery.
If the pipeline used an implicit transaction, then operations that have already executed are rolled back and operations that were queued to follow the failed operation are skipped entirely. The same behavior holds if the pipeline starts and commits a single explicit transaction (i.e. the first statement is BEGIN
and the last is COMMIT
) except that the session remains in an aborted transaction state at the end of the pipeline. If a pipeline contains multiple explicit transactions, all transactions that committed prior to the error remain committed, the currently in-progress transaction is aborted, and all subsequent operations are skipped completely, including subsequent transactions. If a pipeline synchronization point occurs with an explicit transaction block in aborted state, the next pipeline will become aborted immediately unless the next command puts the transaction in normal mode with ROLLBACK
.
Note
The client must not assume that work is committed when it sends a COMMIT
— only when the corresponding result is received to confirm the commit is complete. Because errors arrive asynchronously, the application needs to be able to restart from the last received committed change and resend work done after that point if something goes wrong.
32.5.1.4. Interleaving Result Processing and Query Dispatch #
To avoid deadlocks on large pipelines the client should be structured around a non-blocking event loop using operating system facilities such as select
, poll
, WaitForMultipleObjectEx
, etc.
The client application should generally maintain a queue of work remaining to be dispatched and a queue of work that has been dispatched but not yet had its results processed. When the socket is writable it should dispatch more work. When the socket is readable it should read results and process them, matching them up to the next entry in its corresponding results queue. Based on available memory, results from the socket should be read frequently: there's no need to wait until the pipeline end to read the results. Pipelines should be scoped to logical units of work, usually (but not necessarily) one transaction per pipeline. There's no need to exit pipeline mode and re-enter it between pipelines, or to wait for one pipeline to finish before sending the next.
An example using select()
and a simple state machine to track sent and received work is in src/test/modules/libpq_pipeline/libpq_pipeline.c
in the PostgreSQL source distribution.
32.5.2. Functions Associated with Pipeline Mode #
PQpipelineStatus
#Returns the current pipeline mode status of the libpq connection.
PGpipelineStatus PQpipelineStatus(const PGconn *conn);
PQpipelineStatus
can return one of the following values:-
PQ_PIPELINE_ON
The libpq connection is in pipeline mode.
-
PQ_PIPELINE_OFF
The libpq connection is not in pipeline mode.
-
PQ_PIPELINE_ABORTED
The libpq connection is in pipeline mode and an error occurred while processing the current pipeline. The aborted flag is cleared when
PQgetResult
returns a result of typePGRES_PIPELINE_SYNC
.
-
PQenterPipelineMode
#Causes a connection to enter pipeline mode if it is currently idle or already in pipeline mode.
int PQenterPipelineMode(PGconn *conn);
Returns 1 for success. Returns 0 and has no effect if the connection is not currently idle, i.e., it has a result ready, or it is waiting for more input from the server, etc. This function does not actually send anything to the server, it just changes the libpq connection state.
PQexitPipelineMode
#Causes a connection to exit pipeline mode if it is currently in pipeline mode with an empty queue and no pending results.
int PQexitPipelineMode(PGconn *conn);
Returns 1 for success. Returns 1 and takes no action if not in pipeline mode. If the current statement isn't finished processing, or
PQgetResult
has not been called to collect results from all previously sent query, returns 0 (in which case, usePQerrorMessage
to get more information about the failure).PQpipelineSync
#Marks a synchronization point in a pipeline by sending a sync message and flushing the send buffer. This serves as the delimiter of an implicit transaction and an error recovery point; see Section 32.5.1.3.
int PQpipelineSync(PGconn *conn);
Returns 1 for success. Returns 0 if the connection is not in pipeline mode or sending a sync message failed.
PQsendPipelineSync
#Marks a synchronization point in a pipeline by sending a sync message without flushing the send buffer. This serves as the delimiter of an implicit transaction and an error recovery point; see Section 32.5.1.3.
int PQsendPipelineSync(PGconn *conn);
Returns 1 for success. Returns 0 if the connection is not in pipeline mode or sending a sync message failed. Note that the message is not itself flushed to the server automatically; use
PQflush
if necessary.PQsendFlushRequest
#Sends a request for the server to flush its output buffer.
int PQsendFlushRequest(PGconn *conn);
Returns 1 for success. Returns 0 on any failure.
The server flushes its output buffer automatically as a result of
PQpipelineSync
being called, or on any request when not in pipeline mode; this function is useful to cause the server to flush its output buffer in pipeline mode without establishing a synchronization point. Note that the request is not itself flushed to the server automatically; usePQflush
if necessary.
32.5.3. When to Use Pipeline Mode #
Much like asynchronous query mode, there is no meaningful performance overhead when using pipeline mode. It increases client application complexity, and extra caution is required to prevent client/server deadlocks, but pipeline mode can offer considerable performance improvements, in exchange for increased memory usage from leaving state around longer.
Pipeline mode is most useful when the server is distant, i.e., network latency (“ping time”) is high, and also when many small operations are being performed in rapid succession. There is usually less benefit in using pipelined commands when each query takes many multiples of the client/server round-trip time to execute. A 100-statement operation run on a server 300 ms round-trip-time away would take 30 seconds in network latency alone without pipelining; with pipelining it may spend as little as 0.3 s waiting for results from the server.
Use pipelined commands when your application does lots of small INSERT
, UPDATE
and DELETE
operations that can't easily be transformed into operations on sets, or into a COPY
operation.
Pipeline mode is not useful when information from one operation is required by the client to produce the next operation. In such cases, the client would have to introduce a synchronization point and wait for a full client/server round-trip to get the results it needs. However, it's often possible to adjust the client design to exchange the required information server-side. Read-modify-write cycles are especially good candidates; for example:
BEGIN; SELECT x FROM mytable WHERE id = 42 FOR UPDATE; -- result: x=2 -- client adds 1 to x: UPDATE mytable SET x = 3 WHERE id = 42; COMMIT;
could be much more efficiently done with:
UPDATE mytable SET x = x + 1 WHERE id = 42;
Pipelining is less useful, and more complex, when a single pipeline contains multiple transactions (see Section 32.5.1.3).
[15] The client will block trying to send queries to the server, but the server will block trying to send results to the client from queries it has already processed. This only occurs when the client sends enough queries to fill both its output buffer and the server's receive buffer before it switches to processing input from the server, but it's hard to predict exactly when that will happen.