Hibernate 乐观锁定 Postgres 和 MariaDb 之间的不同行为
Hibernate optimistic locking different behavior between Postgres and 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 数据库启动 50 个线程,我得到 3 个成功的调用,并且 return 值 1、2、3 和其他 47 个失败并出现 ObjectOptimisticLockingFailureException,这是预期的行为 - 这就是我希望应用程序的行为方式。
但是,如果我切换到 MariaDB,则不会发生这种情况。所有 50 个线程都成功完成,我在多个线程中获得相同的响应值,就好像没有乐观锁一样。例如,现在前 5 个线程 returned 1,然后其中 20 个 returned 2,其余的 3 或 4。
为什么会这样?这没有任何意义 - 对于两个数据库,生成的查询都是
update bla set counter=?, version=? where id=? and version=?
但在 Postgresql 中会正确失败,而在 MariaDB 中会意外成功。
唯一可能发生的情况是 MariaDB 有一个错误,因为一旦 Tx 修改了一条记录,它将锁定它直到它提交或回滚。其他 Tx 会因为锁定而阻塞 UPDATE,但必须在释放锁定后重新评估条件。
尝试切换到 READ_COMMITTED 看看它是否解决了问题。这可能是 REPEATABLE_READ 异常。
我找到了解决这个问题的方法。
我好像在 application.properties 中设置了这个 属性:
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 的批处理大小都不兼容。
我刚刚发现,当我对 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 数据库启动 50 个线程,我得到 3 个成功的调用,并且 return 值 1、2、3 和其他 47 个失败并出现 ObjectOptimisticLockingFailureException,这是预期的行为 - 这就是我希望应用程序的行为方式。
但是,如果我切换到 MariaDB,则不会发生这种情况。所有 50 个线程都成功完成,我在多个线程中获得相同的响应值,就好像没有乐观锁一样。例如,现在前 5 个线程 returned 1,然后其中 20 个 returned 2,其余的 3 或 4。
为什么会这样?这没有任何意义 - 对于两个数据库,生成的查询都是
update bla set counter=?, version=? where id=? and version=?
但在 Postgresql 中会正确失败,而在 MariaDB 中会意外成功。
唯一可能发生的情况是 MariaDB 有一个错误,因为一旦 Tx 修改了一条记录,它将锁定它直到它提交或回滚。其他 Tx 会因为锁定而阻塞 UPDATE,但必须在释放锁定后重新评估条件。
尝试切换到 READ_COMMITTED 看看它是否解决了问题。这可能是 REPEATABLE_READ 异常。
我找到了解决这个问题的方法。
我好像在 application.properties 中设置了这个 属性:
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 的批处理大小都不兼容。