如何提高activejdbc的启动性能?

How to improve activejdbc startup performance?

我的组织在我们的 java 网络应用程序中使用带有 javalite + activejdbc 的遗留组件作为 ORM。我正在为开发创建一个本地 docker 数据库 (oracle 12c)。当我启动指向本地数据库的本地码头服务器时,启动时间超过 1 小时。原因是活跃的 jdbc 正在查看所有实体 class 的所有 table 并在循环中为每个实体获取元数据。查看活动 JDBC 注册表 class (org.javalite.activejdbc.Registry) 它这样做:

Connection c = ConnectionsAccess.getConnection(dbName);
java.sql.DatabaseMetaData databaseMetaData = c.getMetaData();
String[] tables = metaModels.getTableNames(dbName);
for (String table : tables) {
    ResultSet rs = databaseMetaData.getColumns(null, schema, tableName, null);
    ...
}

这些调用中的每一个都需要 15-30 秒,并且有数百个实体 classes。当我将我的本地服务器指向我们的测试数据库时,它的速度要快得多(但仍然很慢)。无论如何我可以调整我的本地 docker 数据库以便这些元数据调用更快?或者我可以设置任何 activejdb 配置以使初始化延迟?与我们的测试数据库相比,这些调用在本地数据库上花费的时间要长得多,这一定是有原因的。我不认为这是因为我们的测试数据库是如此强大 - 测试数据库真的很慢并且资源不足。

编辑/澄清: 这似乎确实是一个活跃的 jdbc 问题,更多的是为什么元数据查询在我的本地 docker 数据库上花费这么长时间的问题。下面的代码使用本地数据库 URL 需要 16 秒,指向测试时需要 356 毫秒。我还可以在 docker 图像中看到局部 CPU 峰值达到 100%。

public class DatabaseMetaDataTest {

     public static void main(String args[]) throws SQLException {
          //Registering the Driver
          DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
          //Getting the connection
          String url = "jdbc:oracle:thin:@localhost:1521/ORCLCDB.localdomain";
          //String url = "jdbc:oracle:thin:@test:1532:xe";
          
          Connection con = DriverManager.getConnection(url, "user", "pass");
          System.out.println("Connection established......");
          //Retrieving the meta data object
          DatabaseMetaData metaData = con.getMetaData();
          //Retrieving the columns in the database
          long start = System.currentTimeMillis();
          ResultSet columns = metaData.getColumns(null, "SCHEMA", "TABLE", null);
          long end = System.currentTimeMillis();
          System.out.println("duration:" + (end-start));
          //Printing the column name and size

       }

}

进一步更新: 我反编译了 oracle 驱动程序,发现这是 SQL taking forever:

SELECT  NULL AS table_cat,
       t.owner AS table_schem,
       t.table_name AS table_name,
       t.column_name AS column_name,
       DECODE (t.data_type, 'CHAR', 1, 'VARCHAR2', 12, 'NUMBER', 3,
               'LONG', -1, 'DATE', 93, 'RAW', -3, 'LONG RAW', -4,  
               'BLOB', 2004, 'CLOB', 2005, 'BFILE', -13, 'FLOAT', 6, 
               'TIMESTAMP(6)', 93, 'TIMESTAMP(6) WITH TIME ZONE', -101, 
               'TIMESTAMP(6) WITH LOCAL TIME ZONE', -102, 
               'INTERVAL YEAR(2) TO MONTH', -103, 
               'INTERVAL DAY(2) TO SECOND(6)', -104, 
               'BINARY_FLOAT', 100, 'BINARY_DOUBLE', 101, 
               'XMLTYPE', 2009, 
               1111)
              AS data_type,
       t.data_type AS type_name,
       DECODE (t.data_precision,                null, DECODE(t.data_type,                        'NUMBER', DECODE(t.data_scale,                                    null, 0                                   , 38),          DECODE (t.data_type, 'CHAR', t.char_length,                   'VARCHAR', t.char_length,                   'VARCHAR2', t.char_length,                   'NVARCHAR2', t.char_length,                   'NCHAR', t.char_length,                   'NUMBER', 0,           t.data_length)                           ),         t.data_precision)
              AS column_size,
       0 AS buffer_length,
       DECODE (t.data_type,                'NUMBER', DECODE(t.data_precision,                                 null, DECODE(t.data_scale,                                              null, -127                                             , t.data_scale),                                  t.data_scale),                t.data_scale) AS decimal_digits,
       10 AS num_prec_radix,
       DECODE (t.nullable, 'N', 0, 1) AS nullable,
       NULL AS remarks,
       t.data_default AS column_def,
       0 AS sql_data_type,
       0 AS sql_datetime_sub,
       t.data_length AS char_octet_length,
       t.column_id AS ordinal_position,
       DECODE (t.nullable, 'N', 'NO', 'YES') AS is_nullable,
         null as SCOPE_CATALOG,
       null as SCOPE_SCHEMA,
       null as SCOPE_TABLE,
       null as SOURCE_DATA_TYPE,
       'NO' as IS_AUTOINCREMENT
FROM all_tab_columns t
WHERE t.owner LIKE 'SCHEMA' ESCAPE '/'
  AND t.table_name LIKE 'TABLE' ESCAPE '/'
  AND t.column_name LIKE '%' ESCAPE '/'

ORDER BY table_schem, table_name, ordinal_position

我可以看到当我在 oracle sql 开发人员中 运行 时,我的 sysdba 用户需要 0.5 秒,但其他用户需要 16 秒。仍在调查这些用户之间的区别。

进一步更新... 这似乎是由于 12c 中的一些 oracle 错误。 select * 来自 all_tab_columns 当 运行 以普通用户身份使用时,执行计划有误。它抱怨一些晦涩难懂的 table “X$KZSRO” 进行完整的 table 扫描并永远排序(table 有 2 行 ffs)。当我以 sysdba 身份连接时,它 运行s 更快。我猜普通用户访问这个 table 有一些问题。现在,因为这只是开发数据库,​​所以我将向我的用户授予 sysdba 角色,稍后再找出一些 sql 配置文件。我知道它不是很好的解决方案,但它修复了 oracle 中的性能错误。启动时间从 1 小时缩短到 1 分钟。

首先,如果从数据库中获取元数据每 table 需要 15 到 30 秒,那一定有什么地方非常错误 与该数据库。 ActiveJDBC 使用动态发现以便在每次启动时与数据库同步。这是默认行为。

不过,如果你愿意,你可以使用Static Metadata Generation。 使用此方法,将在构建期间收集所有数据库元数据,并将作为文件打包到您的 jar 中。然后 ActiveJDBC 将立即在所有其他环境中启动,因为它将从该文件而不是数据库中读取元数据。

显然,您必须确保构建时的数据库与您的其他数据库具有完全相同的模式。否则,您将遇到一些映射问题。

虽然静态元数据生成将解决您的启动性能问题,但您的数据库仍然存在问题,我强烈建议您调查一下。

注意:ActiveJDBC 的第一个实现是在 2009 年为 Humana 实现的,我们也使用了 Oracle 数据库。我们当时的架构大约需要 120 table 秒,而且 ActiveJDBC 始终以闪电般的速度启动。

这个问题实际上不是 activejdbc 问题,而是 oracle 12c 问题。似乎从 12c 上的 ALL_TAB_COLUMNS table 读取性能不佳/查询计划不佳,除非您以 sysdba 身份登录。它并不是一个很好的解决方案,但它适用于本地 docker 开发数据库,​​所以我只是将我的用户设置为 sysdba。总有一天会找到一些 sql 配置文件作为真正的解决方案。 sysdba 不能用于任何产品环境,但它适合我用于本地主机开发数据库。

grant sysdba to my_user;

码头-env.xml:

<New class="oracle.jdbc.pool.OracleDataSource">
  <Set name="URL">jdbc:oracle:thin:@localhost:1521/ORCLCDB.localdomain</Set>
  <Set name="user">my_user as sysdba</Set>
  <Set name="password">my_password</Set>
</New>