Как я могу сообщить PostgreSQL о том, чтобы не прервать всю транзакцию, когда одно ограничение завершилось неудачей?

Postgres автоматически прерывает транзакции всякий раз, когда какой-либо оператор SQL заканчивается ошибкой, которая включает в себя любое нарушение ограничения. Например:

glyph=# create table foo (bar integer, constraint blug check(bar > 5));
CREATE TABLE
glyph=# begin;
BEGIN
glyph=# insert into foo values (10);
INSERT 0 1
glyph=# insert into foo values (1);
ERROR:  new row for relation "foo" violates check constraint "blug"
STATEMENT:  insert into foo values (1);
ERROR:  new row for relation "foo" violates check constraint "blug"

Пока ничего не выдано, но транзакция отменяется. Моя личная любимая строка этого сеанса следующая:

glyph=# commit;
ROLLBACK

... так как "ROLLBACK" похоже на нечетное сообщение успеха для COMMIT. Но, действительно, он был откат, и в таблице нет строк:

glyph=# select * from foo;
 bar 
-----
(0 rows)

Я знаю, что могу создать тонну SAVEPOINT и обрабатывать ошибки SQL таким образом, но это связано с большим количеством трафика в базе данных, большей задержкой (возможно, мне придется обрабатывать ошибку из SAVEPOINT), за относительно небольшую выгоду. Я просто хочу обрабатывать ошибку на моем языке приложения в любом случае (Python) с помощью try/except, поэтому единственное, что я хочу от SQL, - это ошибки, которые не запускают автоматические откаты. Что я могу сделать?

Ответ 1

Я очень новичок в PostgreSQL, но один из примеров в документации PostgreSQL для программирования триггеров/серверов выглядит так, как будто он делает именно то, что вы ищете.

Смотрите: http://www.postgresql.org/docs/9.2/static/trigger-example.html

Фрагмент со страницы: "Таким образом, триггер действует как непустое ограничение, но не прерывает транзакцию".

Ответ 2

Я бы настоятельно предложил SqlAlchemy и использовать субтранзакции. Вы можете указать код:

#some code 
Session.begin(subtransactions=True)
#more stuff including sql activity then:
with Session.begin(nested=True):
    # get the security
    try:
       foo = MyCodeToMakeFOO(args)
       Session.add(foo)
       Session.flush()
    except:
       log.error("Database hated your foo(%s) but I'll do the rest"%foo)

Наиболее полезно, когда субтранзакция находится в цикле, где вы хотите обработать хорошие записи и записать плохие.

Ответ 3

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

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

Итак, зная, что PostgreSQL будет управлять своей собственной стратегией отката, вопрос, который нужно задать, "как я могу расширить стратегию отката?" . То, о чем вы сначала подумали, , что привело к сбою транзакции?. В своей структуре try/catch попытайтесь обработать все возможные исключения и снова запустить транзакцию или отправить обратную связь в интерфейсное приложение с некоторыми соответствующими сообщениями "не делать". Для меня это лучший способ обработки вещей, это меньше кода, меньше накладных расходов, большего контроля, более удобного для пользователя, и ваша база данных будет вам благодарна.

Последняя точка, которую я хочу пролить, стандарт SQL имеет код sqlstate, который может использоваться для связи с внутренними модулями. Неисправная операция во время транзакции вернет код sqlstate, и вы сможете использовать эти коды для устранения недостатков. Вы можете создавать свои собственные коды sqlstate, если они не работают с зарезервированными (https://www.postgresql.org/message-id/20185.1359219138%40sss.pgh.pa.us). Например, в функции plpgsql

...
$$
begin
...do something...if it goes wrong
raise exception 'custom exception message' using errcode='12345';
end
$$
...

Это пример использования PDO в PHP (с использованием кода ошибки выше):

...
$pdo->beginTransaction();
try {
  $s = $pdo->prepare('...');
  $s->execute([$value]);

  /**
   * Simulate a null violation exception
   * If it fails, PDO will not wait the commit
   * and will throw the exception, the code below
   * is not executed.
   */
  $s->execute([null]);

  /**
   * If nothing wrong happened, we commit to change
   * the database state.
   */
  $pdo->commit();
}
catch (PDOException $e) {
  /**
   * It is important to also have the commit here.
   * It will trigger PostgreSQL rollback.
   * And make the pdo Object back to auto-commit mode.
   */
  $pdo->commit();

  if ($e->getCode() === '12345') {
    send_feedback_to_client('please do not hack us.');
  }
}
...