Считает ли PostgreSQL вложенные операторы BEGIN и END, даже если он не поддерживает автономные транзакции?

Я работаю над некоторым кодом C ++, который использует libpq для взаимодействия с базой данных PostgreSQL, и я начал писать некоторые функции, каждая из которых внутренне запускает транзакцию, применяет несколько изменений к БД, а затем завершает транзакцию.

Теперь я хотел бы вызвать одну из этих функций в сочетании с другим оператором DML, и все они выполняются в одной транзакции. Примерно так (примечание: очень упрощенно):

void doFoo(PGconn* con) {
    PQexec(con, "BEGIN" );
    PQexec(con, "insert into ..." );
    PQexec(con, "delete from ..." );
    PQexec(con, "END" );
}

void doFooPlus(PGconn* con) {
    PQexec(con, "BEGIN" );
    doFoo(con);
    PQexec(con, "update ..." );
    PQexec(con, "END" );
}

void main(void) {
    doFooPlus(con);
}

Однако, судя по всему, что я прочитал, похоже, что PostgreSQL может не учитывать такого рода вложенность транзакций. Я хочу внести ясность: мне не нужны автономные транзакции, которые, как я знаю, PostgreSQL не поддерживает, и мне не нужны какие-либо явные функции отката к вложенному (или другому) оператору BEGIN, который может быть выполнен с помощью точек сохранения, и очевидно, это не то, что приведенный выше код пытается сделать в любой момент. Я просто хочу подтвердить, будет ли приведенный выше код делать то, что можно было бы надеяться, исходя из структуры кода.

Позвольте мне попытаться уточнить дальше. Вот что PostgreSQL в конечном итоге увидит из приведенного выше кода C ++ (ну, на самом деле, просто C):

BEGIN
BEGIN
insert into ...
delete from ...
END
update ...
END

Что меня беспокоит, так это то, что второй вызов BEGIN полностью игнорируется, и поэтому первый вызов END завершит транзакцию, начатую первым вызовом BEGIN, и поэтому оператор обновления не будет включен в атомарности блока транзакции.

Согласно http://www.postgresql.org/docs/9.4/static/sql-begin.html:

Выдача BEGIN уже внутри блока транзакции вызовет предупреждающее сообщение. На состояние транзакции это не влияет. Чтобы вложить транзакции в блок транзакции, используйте точки сохранения (см. SAVEPOINT).

Комментарий о точках сохранения кажется мне вводящим в заблуждение; PostgreSQL не поддерживает вложенные (автономные) транзакции; точки сохранения предоставляют только способ отката к точке в середине существующей транзакции. Точки сохранения сами по себе не являются транзакциями, поэтому я не могу заменить ими BEGIN и END в doFoo(), потому что тогда я не смогу вызвать doFoo() сам по себе (то есть не из doFooPlus()) и все равно получить атомарность транзакции для вставки и удаления, выполняемой doFoo().

Комментарий о состоянии транзакции «не [не] затронутой» вложенным BEGIN, похоже, подразумевает, что PostgreSQL не будет ее считать и фактически полностью проигнорирует, но цитируемая фраза не совсем < / em> проясни мне, что я не собирался спрашивать об этом в Stack Overflow. Я все еще держу унцию надежды, что PostgreSQL по-прежнему будет считать вложенный BEGIN на какой-то внутренний «уровень вложенности», который будет уменьшаться первым END, а затем снова уменьшаться вторым END, вызывая всю последовательность операторов, которые будут рассматриваться как одна атомарная транзакция.

Итак, может кто-нибудь подтвердить, делает ли PostgreSQL это или нет? А если нет, не могли бы вы дать рекомендации, как лучше всего решить эту проблему в моем коде? Я думал о добавлении параметра bool, чтобы позволить вызывающему doFoo() указать, создавать ли транзакцию или нет, которой doFooPlus() может передать false.

Изменить: для всех, кому интересно, сегодня я понял, что могу довольно легко проверить этот вопрос сам, просто написав программу, которая делает что-то вроде того, что пытается сделать приведенный выше пример кода, а затем изучив ее влияние на базу данных.

Я не буду вдаваться в подробности о внутреннем устройстве программы, но приведенная ниже команда в основном запускает create table t1 (a int, b int, c int ); insert into t1 (a,b,c) values (0,0,0);, затем запускает каждый из заданных операторов SQL по порядку и, наконец, печатает результирующие данные таблицы, так что вы можете видеть, что второй begin и последний rollback были полностью проигнорированы:

> pgtestabc begin begin 'update t1 set a=1;' 'update t1 set b=1;' end 'update t1 set c=1;' rollback;
executing "begin"...done
executing "begin"...done
executing "update t1 set a=1;"...done
executing "update t1 set b=1;"...done
executing "end"...done
executing "update t1 set c=1;"...done
executing "rollback"...done
1|1|1

Также обратите внимание, что вы не можете выполнить этот точный тест, просто запустив операторы SQL в пакете из клиента с графическим интерфейсом, такого как pgAdmin III. Этот конкретный клиент, кажется, творит чудеса с транзакциями; кажется, пакет заключен в неявную транзакцию, так что оператор rollback вызовет откат предыдущих операторов (даже если вы также получаете сообщение «ВНИМАНИЕ: транзакция не выполняется» на панели сообщений ... ), за исключением того, что он также каким-то образом уважает _14 _..._ 15_ блоков (игнорируя вложенные begin операторы, как показано выше) в пакете, что до смешения кажется ужасно похожим на автономные транзакции, которые postgres не поддерживает, поскольку Я считаю, что мы установили в этой теме. Так, например, если вы запустите указанные выше 7 операторов непосредственно в pgAdmin III, вы получите данные 1|1|0.

Но независимо от этой непреднамеренной обфускации, неопровержимый вывод состоит в том, что postgres не считает уровни вложенности _18 _..._ 19_ блоков, поэтому вы должны позаботиться о том, чтобы когда-либо помещать себя только на один верхний уровень begin. .._ 21_ блок.


person bgoldst    schedule 11.01.2015    source источник
comment
Postgresql поддерживает автономные субтранзакции (AST), wiki.postgresql.org/wiki/Autonomous_subtransactions   -  person Jérôme Radix    schedule 11.01.2015
comment
Спасибо за информацию, про AST я не знал. Однако это мне не помогает, так как я хочу иметь возможность вызвать doFoo() и получить обычную (не подпрограмму) транзакцию, но я также хочу иметь возможность вызывать doFooPlus() и получать всю последовательность операторов, включая doFoo(), в атомарной транзакции. Произойдет ли это так, как я написал пример кода в моем вопросе?   -  person bgoldst    schedule 11.01.2015
comment
@ Jérôme: Эта страница описывает автономные субтранзакции как общую концепцию, но нигде не упоминается, что Postgres действительно их реализует (и я почти уверен, что это не так).   -  person Nick Barnes    schedule 12.01.2015
comment
Я согласен с @NickBarnes, это всего лишь обсуждение, руководство для возможной будущей реализации. Я использовал postgresql с c (для игры в грязь). Раньше я писал функции postgresql и вызывал их из c. Если вам действительно нужно обрабатывать транзакции между большим количеством функций C ++, да, лучше всего знать состояние транзакции ok. Как администратор баз данных я не люблю транзакции, управляемые клиентом. Я думаю, что поместить их в функцию pgsql безопаснее.   -  person user_0    schedule 12.01.2015


Ответы (1)


Как сказано в документации, второй BEGIN не влияет на состояние транзакции. Это означает, что нельзя игнорировать следующие COMMIT. Итак, первый COMMIT действительно зафиксирует транзакцию, и все после этого будет продолжаться, как вы описали.

Самое простое решение - переложить ответственность за управление транзакциями на вызывающего. Поместите свой BEGIN / COMMIT вне вызова doFooPlus(), и вам больше не нужно будет беспокоиться о том, какая подпрограмма отвечает за инициирование транзакции. Еще проще: реализовать foo() и fooPlus() как функции на стороне сервера, которые по своей сути атомарны, и вообще забыть об управлении транзакциями на стороне клиента.

Тем не менее, вид специального шаблона управления транзакциями в вашем примере может быть полезен. В конечном счете, реализация этого потребует просто передачи некоторой дополнительной информации вместе с указателем PGConn и ее проверки каждые BEGIN / COMMIT / ROLLBACK.

Конечно, вся эта схема немного громоздка; если вам нужно сделать это более двух раз, вам нужно что-то, что инкапсулирует соединение, функции управления транзакциями и состояние (простой счетчик глубины, вероятно, сделает это). Но прежде чем приступить к написанию собственного API, я бы посмотрел на то, что уже есть.

person Nick Barnes    schedule 12.01.2015
comment
Что касается вашего комментария о том, что документация по точкам сохранения вводит в заблуждение, я не согласен. Когда люди говорят о вложении транзакций, они говорят о вложении атомарных единиц работы, а не просто вложении BEGIN команд. И если вам нужно поведение вложенной транзакции, точки сохранения предоставят вам именно то, что вам нужно, хотя соединение может быть не сразу очевидным. - person Nick Barnes; 12.01.2015