为何设置ddl-auto:update时JPA(Hibernate)启动极慢?
问题:Hibernate ddl-auto:update 启动极慢排查与解决
当设置ddl-auto:update时,Hibernate启动耗时5至20分钟,设置为none则无此问题。调试发现代码卡在org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl类的populateTablesWithColumns方法的while循环中。
项目环境
- 数据库:Oracle 11g(包含3000多张表及数百万条数据)
- Hibernate版本:5.4.x
- ResultSet类型:
com.zaxxer.hikari.pool.HikariProxyResultSet
卡顿方法代码
private void populateTablesWithColumns( String catalogFilter, String schemaFilter, NameSpaceTablesInformation tables) { try { ResultSet resultSet = extractionContext.getJdbcDatabaseMetaData().getColumns( catalogFilter, schemaFilter, null, "%" ); try { String currentTableName = ""; TableInformation currentTable = null; while ( resultSet.next() ) { if ( !currentTableName.equals( resultSet.getString( "TABLE_NAME" ) ) ) { currentTableName = resultSet.getString( "TABLE_NAME" ); currentTable = tables.getTableInformation( currentTableName ); } if ( currentTable != null ) { final ColumnInformationImpl columnInformation = new ColumnInformationImpl( currentTable, DatabaseIdentifier.toIdentifier( resultSet.getString( "COLUMN_NAME" ) ), resultSet.getInt( "DATA_TYPE" ), new StringTokenizer( resultSet.getString( "TYPE_NAME" ), "() " ).nextToken(), resultSet.getInt( "COLUMN_SIZE" ), resultSet.getInt( "DECIMAL_DIGITS" ), interpretTruthValue( resultSet.getString( "IS_NULLABLE" ) ) ); currentTable.addColumn( columnInformation ); } } } finally { resultSet.close(); } } catch (SQLException e) { throw convertSQLException( e, "Error accessing tables metadata" ); } }
调试调用栈
populateTablesWithColumns:368, InformationExtractorJdbcDatabaseMetaDataImpl (org.hibernate.tool.schema.extract.internal) getTables:341, InformationExtractorJdbcDatabaseMetaDataImpl (org.hibernate.tool.schema.extract.internal) getTablesInformation:120, DatabaseInformationImpl (org.hibernate.tool.schema.extract.internal) performTablesMigration:65, GroupedSchemaMigratorImpl (org.hibernate.tool.schema.internal) performMigration:207, AbstractSchemaMigrator (org.hibernate.tool.schema.internal) doMigration:114, AbstractSchemaMigrator (org.hibernate.tool.schema.internal) performDatabaseAction:184, SchemaManagementToolCoordinator (org.hibernate.tool.schema.spi) process:73, SchemaManagementToolCoordinator (org.hibernate.tool.schema.spi) <init>:318, SessionFactoryImpl (org.hibernate.internal) build:468, SessionFactoryBuilderImpl (org.hibernate.boot.internal) build:1259, EntityManagerFactoryBuilderImpl (org.hibernate.jpa.boot.internal) createContainerEntityManagerFactory:58, SpringHibernateJpaPersistenceProvider (org.springframework.orm.jpa.vendor) createNativeEntityManagerFactory:365, LocalContainerEntityManagerFactoryBean (org.springframework.orm.jpa) buildNativeEntityManagerFactory:409, AbstractEntityManagerFactoryBean (org.springframework.orm.jpa) afterPropertiesSet:396, AbstractEntityManagerFactoryBean (org.springframework.orm.jpa) afterPropertiesSet:341, LocalContainerEntityManagerFactoryBean (org.springframework.orm.jpa) invokeInitMethods:1863, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) initializeBean:1800, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) doCreateBean:620, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) createBean:542, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support) getObject:-1, AbstractBeanFactory$$Lambda$293/0x0000000800e57b40 (org.springframework.beans.factory.support) getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support) doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support) getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support) getBean:1154, AbstractApplicationContext (org.springframework.context.support) finishBeanFactoryInitialization:908, AbstractApplicationContext (org.springframework.context.support) refresh:583, AbstractApplicationContext (org.springframework.context.support) refresh:145, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context) refresh:780, SpringApplication (org.springframework.boot) refreshContext:453, SpringApplication (org.springframework.boot) run:343, SpringApplication (org.springframework.boot) run:1370, SpringApplication (org.springframework.boot) run:1359, SpringApplication (org.springframework.boot) main:19, GroupApplication (com.mycompany.group)
原因分析
- 全量元数据查询开销:
ddl-auto:update模式下,Hibernate调用getColumns(null, schemaFilter, null, "%")查询当前数据库所有表的所有列元数据。Oracle 11g在表量达3000+时,全量元数据查询本身就极慢,加上网络IO和结果集遍历,直接导致启动卡顿。 - 无效遍历浪费资源:循环会处理所有查询到的表列,但实际只需处理项目实体映射的表,大量非项目表的元数据加载和遍历属于无效开销。
- Oracle JDBC元数据查询性能短板:Oracle 11g的JDBC驱动默认实现对
getColumns查询的优化不足,表数量多的时候,底层SQL执行效率极低。
解决方案与优化建议
1. 精准过滤元数据查询范围
通过配置限制Hibernate只查询项目对应的schema和表,避免全量扫描:
# 指定项目使用的schema spring.jpa.properties.hibernate.default_schema=YOUR_PROJECT_SCHEMA # 自定义Schema过滤,仅加载项目映射的表 spring.jpa.properties.hibernate.schema_filter_provider=com.yourcompany.CustomSchemaFilterProvider
自定义CustomSchemaFilterProvider示例:
public class CustomSchemaFilterProvider implements SchemaFilterProvider { @Override public SchemaFilter getCreateFilter() { return includeProjectTables(); } @Override public SchemaFilter getDropFilter() { return includeProjectTables(); } @Override public SchemaFilter getMigrateFilter() { return includeProjectTables(); } @Override public SchemaFilter getValidateFilter() { return includeProjectTables(); } private SchemaFilter includeProjectTables() { return new SchemaFilter() { // 项目实体对应的表名集合 private final Set<String> allowedTables = Set.of("USER", "ORDER", "PRODUCT"); @Override public boolean includeNamespace(Namespace namespace) { return true; } @Override public boolean includeTable(TableIdentifier tableIdentifier) { return allowedTables.contains(tableIdentifier.getTableName()); } }; } }
2. 替换ddl-auto模式(推荐)
- 生产环境:禁用
update模式,改用none,通过专业数据库迁移工具(Flyway、Liquibase)管理Schema变更,既消除启动卡顿,又能精准控制Schema版本。 - 开发环境:可使用
create-drop或配合元数据过滤的update,或本地改用H2轻量数据库开发,避免直接连接Oracle。
3. 优化Oracle JDBC配置
- 升级Oracle JDBC驱动至最新兼容版本(如ojdbc8),新版本对元数据查询有性能优化。
- 添加JDBC连接参数优化结果集获取:
spring.datasource.url=jdbc:oracle:thin:@//your-db-host:1521/your-db-service?defaultRowPrefetch=1000&useFetchSizeWithLongColumn=true
defaultRowPrefetch设置批量获取结果集行数,减少网络交互次数。
4. 替换JPA实现(备选)
若Hibernate的元数据处理性能无法满足需求,可替换为EclipseLink:
- 添加依赖:
<dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>org.eclipse.persistence.jpa</artifactId> <version>2.7.10</version> </dependency>
- 配置JPA实现:
spring.jpa.provider=org.eclipse.persistence.jpa.PersistenceProvider spring.jpa.properties.eclipse.persistence.ddl-generation=update spring.jpa.properties.eclipse.persistence.default-schema=YOUR_PROJECT_SCHEMA
EclipseLink对Oracle元数据查询的优化更到位,大量表场景下Schema更新性能优于Hibernate。
内容的提问来源于stack exchange,提问作者Xi Minghui
相关产品推荐
相关产品推荐

