Я проверил это с помощью следующей конфигурации pgbouncer:
[pgbouncer]
pool_mode = transaction
server_reset_query = DISCARD ALL
server_check_query = select 1
server_reset_query_always = 0
max_client_conn = 2000
default_pool_size = 1
min_pool_size = 1
reserve_pool_size = 0
reserve_pool_timeout = 5
max_db_connections = 1
и двух параллельных подключений к одной базе данных через pgbouncer. Далее используется P1 и P2 в качестве идентификаторов для запросов, отправляемых каждым процессом. Если выполнить простой тест, такой как:
P1: set application_name=p1;
P1: select pg_advisory_lock(42);
P2: set application_name=p2;
P1: show application_name;
p1
P2: select pg_advisory_lock(42);
P2. show application_name;
p2
... , кажется, что оба соединения смогли получить одну и ту же исключительную консультативную блокировку, даже если они имеют уникальные значения application_name, поэтому каждое соединение должно быть уникально! В действительности все команды были отправлены на postgres с использованием одного соединения, поэтому это единственное соединение дважды получило консультативную блокировку 42, и все в порядке, если позднее она дважды снимает такую же блокировку (логически один раз для P1 и P2 соединение). Это происходит из-за того, что один и тот же владелец может неоднократно снимать монопольные блокировки и должен сниматься одинаково много раз, прежде чем любой другой процесс сможет получить такую же блокировку.
В результате также возможно следующее:
P1: select pg_advisory_lock(42);
P2: select pg_advisory_lock(42);
P1: begin transaction isolation level serializable;
P1: select 1;
P1: commit;
P2: begin transaction isolation level serializable;
P2: select 1;
P2: commit;
P1: select pg_advisory_unlock(42);
P2: select pg_advisory_unlock(42);
Представьте несколько запросов и изменений в базе данных вместо select 1
выше.
Обратите внимание, что оба клиентских приложения базы данных получили эксклюзивную блокировку перед выполнением сериализуемой транзакции, и все еще не работали правильно.
Очевидно, что подобная ситуация невозможна, если P1 и P2 фактически не используют одно и то же соединение с базой данных. Обычно можно разрешить более одного параллельного соединения с базой данных через pgbouncer, так что код такого типа является довольно редким. На практике это будет работать до тех пор, пока вы не достигнете высокой нагрузки на сервер, а затем вы получите случайные сбои, зависящие от того, какой запрос выполняется вместе с другим запросом от другого клиента.
В результате, это определенно НЕ безопасно выполнять любые запросы вне транзакции, если pool_mode=transaction
активно. Опция server_reset_query_always=1
не может действительно решить проблему и никогда не должна использоваться. Если вы чувствуете, что вам нужно server_reset_query_always=1
, вам нужно использовать pool_mode=session
вместо этого или вы рискуете случайным повреждением данных.
Кроме того, pgbouncer кажется достаточно умным, чтобы подделать некоторые данные подключения c данные. Например, когда P1 устанавливает свое имя_приложения в первом примере выше и позже запрашивает его после того, как P2 уже установил имя своего приложения в том же соединении, P1 получит ожидаемый результат. Однако, если вы наблюдаете pg_stat_activity
, пока это происходит, единственное активное соединение на postgres меняет значение application_name каждый раз, когда P1 или P2 отправляет запрос. Это заставляет казаться , что этот тип микширования в порядке с pool_mode=transaction
.
В конце, установка application_name
вне транзакции должна быть безопасной, но любая функция, фактически реализованная на * Уровень 1053 * небезопасен. Если вы не абсолютно уверены, , что pgbouncer может эмулировать нужную вам функцию, не отправляйте никаких запросов к соединению с базой данных, полученных от pgbouncer в pool_mode=transaction
, за исключением begin ...
, commit ...
или rollback
. Когда транзакция активна, соединение зарезервировано для вас, и все работает так же, как и при прямом прямом соединении с postgres, пока вы не выполните commit
или rollback
.
Мне бы очень хотелось, чтобы pgbouncer всегда возвращать ошибку каждый раз, когда клиент пытается выполнить любой запрос вне транзакции, если установлено pool_mode=transaction
. К сожалению, это не та реальность, в которой мы живем, и клиенты pgbouncer должны быть осторожны.