You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何设置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)

原因分析

  1. 全量元数据查询开销:ddl-auto:update模式下,Hibernate调用getColumns(null, schemaFilter, null, "%")查询当前数据库所有表的所有列元数据。Oracle 11g在表量达3000+时,全量元数据查询本身就极慢,加上网络IO和结果集遍历,直接导致启动卡顿。
  2. 无效遍历浪费资源:循环会处理所有查询到的表列,但实际只需处理项目实体映射的表,大量非项目表的元数据加载和遍历属于无效开销。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 00:40:27