如何使用Spring重试PostgreSQL可序列化事务?

pbi*_*len 5 postgresql spring transactions spring-jdbc spring-retry

我正在针对PostgreSQL v12数据库进行开发。我正在使用SERIALIZABLE交易。一般想法是,当PostgreSQL检测到序列化异常时,应重试完整的事务。

我正在使用Spring的AbstractFallbackSQLExceptionTranslator将数据库异常转换为Spring的异常类。此异常翻译器应将PostgreSQL错误40001/serialization_failure转换为ConcurrencyFailureException。Spring JDBC维护一个映射文件,以将PostgreSQL特定的代码映射到数据库异常40001的通用cannotSerializeTransactionCodes类,该类将转换ConcurrencyFailureException为API用户。

我的想法是依靠Spring Retry项目重试SERIALIZABLE由于序列化错误而暂停的事务,如下所示:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(include = ConcurrencyFailureException.class, maxAttempts = ..., backoff = ...)
@Transactional(isolation = Isolation.SERIALIZABLE)
public @interface SerializableTransactionRetry {
}
Run Code Online (Sandbox Code Playgroud)

在服务实现,我会简单地替换@Transactional通过@SerializableTransactionRetry,并用它做。

现在,回到PostgreSQL。本质上,可以在两个阶段检测序列化异常:

  1. 在执行语句期间
  2. 在事务的提交阶段

似乎Spring AbstractFallbackSQLExceptionTranslator正确地转换了在执行语句期间检测到的序列化异常,但在提交阶段未能转换序列化异常。考虑以下堆栈跟踪:

org.springframework.transaction.TransactionSystemException: Could not commit JDBC transaction; nested exception is org.postgresql.util.PSQLException: 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.
    at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:332)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.retry.interceptor.RetryOperationsInterceptor$1.doWithRetry(RetryOperationsInterceptor.java:91)
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
    at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:118)
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
Run Code Online (Sandbox Code Playgroud)

如您所见,PostgreSQL检测到序列化异常(ERROR: could not serialize access due to ...),但是Spring将其转换为TransactionSystemException而不是ConcurrencyFailureException

我可以更改SerializableTransactionRetry上面的注释,使其也包含一个TransactionSystemException,但是我认为那是错误的,因为现在我们将重试任何类型的事务错误,这不是我们想要的。

这是Spring的缺点AbstractFallbackSQLExceptionTranslator吗?我正在使用Spring 5.2.1。

小智 4

正如https://github.com/spring-projects/spring-framework/issues/24064#issuecomment-557800496中所解释的,SQLExceptionTranslator实际上不用于提交阶段发生的 SQL 异常。

在同一张票中,已提出在 Spring 5.3 中引入此功能的提案(我相信将在 2020 年第二季度的某个时间发布)。