Hibernate乐观地锁定Postgres和MariaDB之间的不同行为



我刚刚发现,当我对Postgresql或MariaDB数据库使用乐观锁定时,我的应用程序的行为有所不同,我想知道是否有人可以解释会发生什么以及如何使应用程序以与MariaDB相同的方式工作?我使用Postgresl 10.5和MariaDB 10.3.10与InnoDB引擎和默认设置。我使用Spring框架版本5.1.0和Hibernate 5.3.6。

所以我的代码看起来像这样:

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Bla {
@Id
@GeneratedValue
private long id;
@Version
private long version;
private int counter;
}

我还有一个用于此实体的存储库和以下服务方法:

@Transactional
public int increment(long id) {
Bla bla = blaRepo.getOne(id);
bla.setCounter(bla.getCounter() + 1);
return bla.getCounter();
}

如果我在多个线程上调用此方法,我希望更新只会对其中一个线程成功,如果他们接触具有相同版本的实体。举个例子:如果我在一次运行中使用 Postgres db 启动 50 个线程,我会收到 3 个成功的调用并返回值 1、2、3,其他 47 个失败,并带有预期行为 ObjectOptimisticLockingFailureException - 这就是我希望应用程序的行为方式。

但是,如果我切换到MariaDB,那么这不会发生。所有这 50 个线程都成功完成,我在多个线程中得到相同的响应值,就好像没有乐观锁一样。例如,现在前 5 个线程返回 1,然后其中 20 个返回 2,其余返回 3 或 4。

为什么会这样?这没有任何意义 - 对于两个数据库,生成的查询是

update bla set counter=?, version=? where id=? and version=?

但是在Postgresql中会正确失败,而MariaDB将意外成功。

唯一可能发生的方法是MariaDB有一个错误,因为一旦Tx修改了记录,它就会锁定它,直到它提交或回滚。其他 Tx 会由于锁定而阻止 UPDATE,但在释放锁定后必须重新评估条件。

尝试切换到READ_COMMITTED,看看它是否能解决问题。这可能是一个REPEATABLE_READ异常。

我找到了这个问题的解决方案。

似乎我在应用程序属性中设置了此属性:

spring.jpa.properties.hibernate.jdbc.batch_size = 50

当我使用 Postgresql 时,我得到以下带有两个线程的调试跟踪:

13223 [pool-2-thread-2] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=?
13223 [pool-2-thread-1] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=? 
13226 [pool-1-thread-1] DEBUG org.hibernate.engine.jdbc.batch.internal.BatchingBatch - Executing batch size: 1
13226 [pool-1-thread-2] DEBUG org.hibernate.engine.jdbc.batch.internal.BatchingBatch - Executing batch size: 1
13230 [pool-1-thread-1] ERROR org.hibernate.engine.jdbc.batch.internal.BatchingBatch - HHH000315: Exception executing batch [org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1], SQL: update bla set counter=?, version=? where id=? and version=?

然后使用具有相同批大小 50 的 MariaDB:

21978 [pool-2-thread-2] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=?
21978 [pool-2-thread-1] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=? 
21979 [pool-2-thread-2] DEBUG org.hibernate.engine.jdbc.batch.internal.BatchingBatch - Executing batch size: 1
21979 [pool-2-thread-1] DEBUG org.hibernate.engine.jdbc.batch.internal.BatchingBatch - Executing batch size: 1
21980 [pool-2-thread-2] DEBUG org.hibernate.jdbc.Expectations - Success of batch update unknown: 0
21980 [pool-2-thread-1] DEBUG org.hibernate.jdbc.Expectations - Success of batch update unknown: 0

然后使用批量大小为 1 的 MariaDB:

12994 [pool-2-thread-2] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=?
12994 [pool-2-thread-1] DEBUG org.hibernate.SQL - update bla set counter=?, version=? where id=? and version=?
12997 [pool-2-thread-1] DEBUG org.hibernate.cache.internal.TimestampsCacheEnabledImpl - Invalidating space [bla], timestamp: 6307671153053696
12998 [pool-2-thread-2] DEBUG org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl - JDBC transaction marked for rollback-only (exception provided for stack trace)

现在,应用程序确实抛出了预期的ObjectOptimisticLockingFailureException。

但不幸的是,这意味着在实体上使用乐观锁定的 MariaDb 和任何大于 1 的批大小是不兼容的。

最新更新