org.postgresql.util.PSQLException: ERROR: не удалось сериализовать доступ из-за зависимостей чтения/записи между транзакциями

Вопрос: ОБНОВЛЕНИЕ: в конце концов мне удалось воспроизвести это в минимальной настройке, которую я опубликовал как отдельный вопрос. Я столкнулся с следующим исключением при выполнении вставки JDBC из двух разных приложений, работающих бок о бок в одном экземпляре и таблицах PostgreSQL: org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions [java] ERROR>

Вопрос:

ОБНОВЛЕНИЕ: в конце концов мне удалось воспроизвести это в минимальной настройке, которую я опубликовал как отдельный вопрос.

Я столкнулся с следующим исключением при выполнении вставки JDBC из двух разных приложений, работающих бок о бок в одном экземпляре и таблицах PostgreSQL:

org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions [java] ERROR> Detail: Reason code: Canceled on identification as a pivot, during write. [java] ERROR> Hint: The transaction might succeed if retried.

Исключение произошло при попытке выполнить следующий оператор:

public int logRepositoryOperationStart(String repoIvoid, MetadataPrefix prefix, RepositoryOperation operation, int pid, String command, String from_XMLGregCal) throws SQLException { Connection conn = null; PreparedStatement ps = null; try { conn = getConnection(); conn.commit(); String SQL = «INSERT INTO vo_business.repositoryoperation(ivoid, metadataprefix, operation, i, pid, command, from_xmlgregcal, start_sse) «+ «(SELECT ?, ?, ?, COALESCE(MAX(i)+1,0), ?, ?, ?, ? FROM vo_business.repositoryoperation «+ «WHERE ivoid=? AND metadataprefix=? AND operation=?) «; ps = conn.prepareStatement(SQL); ps.setString(1, repoIvoid); ps.setString(2, prefix.value()); ps.setString(3, operation.value()); ps.setInt (4, pid); ps.setString(5, command); ps.setString(6, from_XMLGregCal); ps.setInt (7, Util.castToIntWithChecks(TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS))); ps.setString(8, repoIvoid); ps.setString(9, prefix.value()); ps.setString(10, operation.value()); if (ps.executeUpdate() != 1) { // line 217 conn.rollback(); throw new RuntimeException(); } conn.commit(); return getMaxI(conn, repoIvoid, prefix, operation); } catch (SQLException e) { conn.rollback(); throw e; } finally { DbUtils.closeQuietly(conn, ps, (ResultSet) null); } }

.. на линии, помеченной line-217 выше. Я предоставляю фактическую трассировку стека в конце.

Уровень изоляции транзакций для Connection conn объекта устанавливается равным SERIALIZABLE в реализации getConnection():

protected Connection getConnection() throws SQLException { Connection conn = ds.getConnection(); conn.setAutoCommit(false); conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); return conn; }

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

Какая ошибка в этом и как я должен заниматься при устранении неполадок? Должен ли я смотреть на уровни изоляции транзакций, целые таблицы или блокировки, зависящие от строки (если есть такая концепция в PostgreSQL) и т.д.? Должен ли я просто повторить попытку (подсказка гласит, что “транзакция может быть успешной, если повторится”). Я попытаюсь воспроизвести его в SSCCE, но я просто опубликую это, если у него есть очевидная причина/решение

[java] ERROR>org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions [java] ERROR> Detail: Reason code: Canceled on identification as a pivot, during write. [java] ERROR> Hint: The transaction might succeed if retried. [java] ERROR> at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102) [java] ERROR> at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835) [java] ERROR> at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257) [java] ERROR> at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500) [java] ERROR> at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:388) [java] ERROR> at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:334) [java] ERROR> at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105) [java] ERROR> at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105) [java] ERROR> at _int.esa.esavo.dbbusiness.DBBusiness.logRepositoryOperationStart(DBBusiness.java:217) [java] ERROR> at _int.esa.esavo.harvesting.H.main(H.java:278) Лучший ответ:

Всякий раз, когда вы запрашиваете SERIALIZABLE изоляцию, БД будет пытаться заставить параллельные наборы запросов казаться выполненными последовательно с точки зрения результатов, которые они производят. Это не всегда возможно, например, когда две транзакции имеют взаимные зависимости. В этом случае PostgreSQL прервет одну из транзакций с ошибкой сериализации, сообщив, что вам следует повторить попытку.

Код, использующий SERIALIZABLE всегда должен быть готов к повторной попытке транзакций. Он должен проверить SQLSTATE и, для ошибок сериализации, повторить транзакцию.

Смотрите документацию по изоляции транзакций.

В этом случае, я думаю, ваше основное заблуждение может заключаться в том, что:

это одна атомная вставка

поскольку это ничего подобного, это INSERT… SELECT который затрагивает vo_business.repositoryoperation как для чтения, так и для записи. Этого вполне достаточно, чтобы создать потенциальную зависимость с другой транзакцией, которая делает то же самое, или той, которая читает и пишет в таблицу другим способом.

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

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

Чтобы знать наверняка, мне нужно было бы видеть обе транзакции рядом, но здесь доказательство, показывающее insert… select может конфликтовать с самим собой. Откройте три сессии psql и запустите:

session0: CREATE TABLE serialdemo(x integer, y integer); session0: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; session0: LOCK TABLE serialdemo IN ACCESS EXCLUSIVE MODE; session1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; session2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; session1: INSERT INTO serialdemo (x, y) SELECT 1, 2 WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1); session2: INSERT INTO serialdemo (x, y) SELECT 1, 2 WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1); session0: ROLLBACK; session1: COMMIT; session2: COMMIT;

session1 совершит нормально. сеанс2 завершится с:

ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt. HINT: The transaction might succeed if retried.

Это не тот же сбой сериализации, как в вашем случае, и он не доказывает, что ваши операторы могут конфликтовать друг с другом, но показывает, что insert… select не настолько атомарна, как вы думали.

Оцените статью
Добавить комментарий