Liquibase创建锁表时触发Oracle ORA-12838异常是什么原因?
问题描述
我配置了一套DropWizard服务,设置为应用启动时自动执行所有Liquibase数据库迁移操作。服务首次启动时,我会调用Liquibase::listLocks()输出相关信息,该调用的附加作用是:如果DATABASECHANGELOG和DATABASECHANGELOGLOCK表不存在,会自动完成创建。
某次在新部署的Oracle ATP DB上运行时,上述listLocks调用在创建锁表的过程中抛出了如下错误:
ERROR 2021-07-08 17:59:22,546 [main] com.blah.utils.LiquibaseMigrator: Error listing locks! liquibase.exception.DatabaseException: ORA-12838: cannot read/modify an object after modifying it in parallel [Failed SQL: (12838) INSERT INTO DATABASECHANGELOGLOCK (ID, LOCKED) VALUES (1, 0)] at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:430) at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:87) at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:159) at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:139) at liquibase.lockservice.StandardLockService.init(StandardLockService.java:128) at liquibase.Liquibase.checkLiquibaseTables(Liquibase.java:1176) at liquibase.Liquibase.listLocks(Liquibase.java:1193) at com.blah.utils.LiquibaseMigrator.dbLocksExist(LiquibaseMigrator.java:xxx) at com.blah.utils.LiquibaseMigratorExecutor.dbLocksExist(LiquibaseMigratorExecutor.java:xxx) at com.blah.MyApp.migrateDatabase(MyApp.java:xxx) at com.blah.MyApp.run(MyApp.java:xxx) at com.blah.MyApp.run(MyApp.java:xxx) at io.dropwizard.cli.EnvironmentCommand.run(EnvironmentCommand.java:59) at io.dropwizard.cli.ConfiguredCommand.run(ConfiguredCommand.java:98) at io.dropwizard.cli.Cli.run(Cli.java:78) at io.dropwizard.Application.run(Application.java:94) at com.blah.MyApp.main(MyApp.java:xxx) Caused by: java.sql.SQLException: ORA-12838: cannot read/modify an object after modifying it in parallel
服务抛出该异常后Pod会自动重启,陷入循环报错状态。我手动向锁表中插入了它尝试写入的那条数据后,下一次启动就成功了。
当时运行环境中只有1个Pod在运行,因此我判断该问题和启动竞态条件漏洞无关,但不清楚为什么会出现cannot read/modify an object after modifying it in parallel的错误状态,请问是什么原因导致该异常?
原因解答
异常触发逻辑
Oracle的ORA-12838错误的核心规则是:同一个事务内,如果一个对象被并行DML/DDL修改过,事务提交前不能再对该对象执行任何读写操作。
你遇到的问题完全符合该规则的触发条件:
- Oracle ATP默认对所有DDL操作启用并行执行特性,
CREATE TABLE操作会被判定为并行修改操作 - 低版本Liquibase的锁表初始化逻辑存在事务时序缺陷:创建
DATABASECHANGELOGLOCK表的DDL和后续插入初始化记录的INSERT在同一个事务内执行,INSERT操作触发了Oracle的并行修改后访问限制
手动插入后恢复的原因
你手动插入初始化记录时,是在独立事务内完成操作,执行完成后事务会自动提交。后续应用启动时锁表已经存在,Liquibase不需要再执行建表的DDL操作,直接读取已有的锁表数据即可,不会触发并行修改限制。
规避方案
- 升级Liquibase到4.6及以上版本,该版本已经修复了锁表初始化的事务时序问题,建表完成后会先提交事务再执行插入操作
- 暂时无法升级的话,可以在JDBC连接参数中添加
parallel_query=false关闭Oracle ATP默认的并行执行特性 - 也可以提前手动创建
DATABASECHANGELOG和DATABASECHANGELOGLOCK两张系统表,避免应用启动时自动执行建表操作
内容的提问来源于stack exchange,提问作者Alex Moore
相关产品推荐
相关产品推荐

