带有 Spring 的 JDBC 缓慢元数据获取 Oracle

发布于 2024-12-26 02:02:54 字数 522 浏览 2 评论 0原文

我正在使用 Spring JdbcUtils.extractDatabaseMetaData() 方法来分析数据库。该函数调用回调并移交 DatabaseMetaData 对象。该对象提供getColumns(String Catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)

我这样称呼它 getColumns("",TABLE_OWNER_USERNAME,null,null) 并得到 400 列结果。这些正是我想要的结果,但请求需要超过 1 分钟。

我可以以某种方式优化这个查询以使其更快吗?拉取 400 行应该在 1 秒内完成,而不是一分钟。

编辑:我不怀疑 Spring 部分很慢。更仔细的分析表明,获取DatabaseMetaData需要几秒钟,但执行getColumns()需要很长时间。

I am using the Spring JdbcUtils.extractDatabaseMetaData() method to analyze the database. The function calls a callback and hands over a DatabaseMetaData object. This object provides the getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern).

I call it like this getColumns("",TABLE_OWNER_USERNAME,null,null) and get 400 columns as a result. These are exactly the results that I want, but the request takes over 1 minute.

Can I somehow optimize this query to be fast? Pulling 400 rows should happen in 1 seconds and not one minute.

EDIT: I don't suspect the Spring part being slow. Closer analysis showed that fetching the DatabaseMetaData takes a few seconds butexecuting the getColumns() takes really long.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

世俗缘 2025-01-02 02:02:54

对客户端和服务器之间的实际通信进行逆向工程后,我可以发现 Oracle 的 DatabaseMetaData.getColumns() 方法发送以下 SQL 查询(尽管这可能会因 ODBC 驱动程序版本和设置而改变):

    declare
    in_owner varchar2(128);
    in_name varchar2(128);
    in_column varchar2(128);
    xyzzy SYS_REFCURSOR;
    begin
    in_owner := :1;  // Which resolves to the schema (user) name supplied
    in_name := :2;   // Which resolves to the table name supplied
    in_column := :3; // Which gets set to '%';
    open xyzzy for
    SELECT NULL AS table_cat,
        t.owner AS table_schem,
        t.table_name AS table_name,
        t.column_name AS column_name,
    DECODE(  (SELECT a.typecode
        FROM ALL_TYPES A
        WHERE a.type_name = t.data_type),
    'OBJECT', 2002,
    'COLLECTION', 2003,
    DECODE(substr(t.data_type, 1, 9),
        'TIMESTAMP',
        DECODE(substr(t.data_type, 10, 1),
            '(',
            DECODE(substr(t.data_type, 19, 5),
                'LOCAL', -102, 'TIME ', -101, 93),
            DECODE(substr(t.data_type, 16, 5),
            'LOCAL', -102, 'TIME ', -101, 93)),
        'INTERVAL ',
        DECODE(substr(t.data_type, 10, 3),
        'DAY', -104, 'YEA', -103),
        DECODE(t.data_type,
        'BINARY_DOUBLE', 101,
        'BINARY_FLOAT', 100,
        'BFILE', -13,
        'BLOB', 2004,
        'CHAR', 1,
        'CLOB', 2005,
        'COLLECTION', 2003,
        'DATE', 93,
        'FLOAT', 6,
        'LONG', -1,
        'LONG RAW', -4,
        'NCHAR', -15,
        'NCLOB', 2011,
        'NUMBER', 2,
        'NVARCHAR', -9,
        'NVARCHAR2', -9,
        'OBJECT', 2002,
        'OPAQUE/XMLTYPE', 2009,
        'RAW', -3,
        'REF', 2006,
        'ROWID', -8,
        'SQLXML', 2009,
        'UROWI', -8,
        'VARCHAR2', 12,
        'VARRAY', 2003,
        '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,
        t.virtual_column as IS_GENERATEDCOLUMN
    FROM all_tab_cols t
    WHERE t.owner LIKE in_owner ESCAPE '/'
    AND t.table_name LIKE in_name ESCAPE '/'
    AND t.column_name LIKE in_column ESCAPE '/'
    AND t.user_generated = 'YES'
    ORDER BY table_schem, table_name, ordinal_position;
    end;

您可以理解为什么这可能有点慢,特别是 ALL_TAB_COLS 和 ALL_TYPES 表都可以有 1000 条记录长。然而,尽管 Oracle 努力执行第一次调用(几分钟),但后续调用几乎立即返回结果。这是一个典型的表连接性能问题,即使需要数据子集,引擎也会在计算和提供所需子集之前连接整个数据集。随后的数据/结果缓存可提高后续查询的性能。

更好的解决方案可能是使用 get_ddl() 并根据 此线程

或者,您可以通过执行虚拟查询然后使用 resultSetMetadata 更快地查询表上的元数据,如下所示(注意:列备注元数据可能无法立即可用):

    ResultSet rs = connection.CreateStatement.executeQuery("SELECT * from MyTable WHERE 1=0");
    ResultSetMetaData md = rs.getMetaData();
    for (int ix = 1; ix <= md.getColumnCount(); ix++)
    {
      int colSize = md.getPrecision(ix);
      String nativeType = md.getColumnTypeName(ix);
      int jdbcType = md.getColumnType(ix);
      // and so on....
    }

Having reverse engineered the actually communications between client and server I can reveal that Oracle's DatabaseMetaData.getColumns() methods sends the following SQL query (though this may change with ODBC driver versions and settings):

    declare
    in_owner varchar2(128);
    in_name varchar2(128);
    in_column varchar2(128);
    xyzzy SYS_REFCURSOR;
    begin
    in_owner := :1;  // Which resolves to the schema (user) name supplied
    in_name := :2;   // Which resolves to the table name supplied
    in_column := :3; // Which gets set to '%';
    open xyzzy for
    SELECT NULL AS table_cat,
        t.owner AS table_schem,
        t.table_name AS table_name,
        t.column_name AS column_name,
    DECODE(  (SELECT a.typecode
        FROM ALL_TYPES A
        WHERE a.type_name = t.data_type),
    'OBJECT', 2002,
    'COLLECTION', 2003,
    DECODE(substr(t.data_type, 1, 9),
        'TIMESTAMP',
        DECODE(substr(t.data_type, 10, 1),
            '(',
            DECODE(substr(t.data_type, 19, 5),
                'LOCAL', -102, 'TIME ', -101, 93),
            DECODE(substr(t.data_type, 16, 5),
            'LOCAL', -102, 'TIME ', -101, 93)),
        'INTERVAL ',
        DECODE(substr(t.data_type, 10, 3),
        'DAY', -104, 'YEA', -103),
        DECODE(t.data_type,
        'BINARY_DOUBLE', 101,
        'BINARY_FLOAT', 100,
        'BFILE', -13,
        'BLOB', 2004,
        'CHAR', 1,
        'CLOB', 2005,
        'COLLECTION', 2003,
        'DATE', 93,
        'FLOAT', 6,
        'LONG', -1,
        'LONG RAW', -4,
        'NCHAR', -15,
        'NCLOB', 2011,
        'NUMBER', 2,
        'NVARCHAR', -9,
        'NVARCHAR2', -9,
        'OBJECT', 2002,
        'OPAQUE/XMLTYPE', 2009,
        'RAW', -3,
        'REF', 2006,
        'ROWID', -8,
        'SQLXML', 2009,
        'UROWI', -8,
        'VARCHAR2', 12,
        'VARRAY', 2003,
        '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,
        t.virtual_column as IS_GENERATEDCOLUMN
    FROM all_tab_cols t
    WHERE t.owner LIKE in_owner ESCAPE '/'
    AND t.table_name LIKE in_name ESCAPE '/'
    AND t.column_name LIKE in_column ESCAPE '/'
    AND t.user_generated = 'YES'
    ORDER BY table_schem, table_name, ordinal_position;
    end;

You can appreciate why that might be a bit slow, especially as the ALL_TAB_COLS and ALL_TYPES tables can each be 1000's of records long. Nevertheless while Oracle struggles to execute the first ever invocation (minutes) subsequent calls return results almost instantly. This is a classic table-join performance issue where even though a subset of data is required the engine joins the whole dataset before calculating and delivering the required subset. Subsequently data/results caching works to improve the performance of subsequent queries.

The better solution might be to use get_ddl() and parse the returned table definition as per this thread.

Alternatively you may be able to query the metadata on a table faster by executing a dummy query then using resultSetMetadata as follows (Note: column remarks metadata may not be immediately available):

    ResultSet rs = connection.CreateStatement.executeQuery("SELECT * from MyTable WHERE 1=0");
    ResultSetMetaData md = rs.getMetaData();
    for (int ix = 1; ix <= md.getColumnCount(); ix++)
    {
      int colSize = md.getPrecision(ix);
      String nativeType = md.getColumnTypeName(ix);
      int jdbcType = md.getColumnType(ix);
      // and so on....
    }
离鸿 2025-01-02 02:02:54

也许这是查询 ALL_TAB_COLUMNS 的更好方法。这是一个示例:

public final List<Column> getColumnsByOwner(final String owner) {
    final String sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, "
            + " DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT"
            + " FROM ALL_TAB_COLUMNS"
            + " WHERE OWNER = ? ORDER BY COLUMN_ID";

    return jdbcTemplate.query(sql,
            new Object[] { owner },
            new RowMapper<Column>() {
                @Override
                public Column mapRow(final ResultSet res, final int rowNum)
                        throws SQLException {
                    final Column reg = new Column();

                    reg.setColumnName(res.getString("COLUMN_NAME"));
                    //Read other properties
                    reg.setNullable(res.getString("NULLABLE").equals("Y"));
                    return reg;
                }
            });
}

如果您需要按表过滤,只需添加“ AND TABLE_NAME = ?”将sql和tableName作为另一个参数。

希望有帮助。

Maybe it's a better approach to query ALL_TAB_COLUMNS. Here is an example:

public final List<Column> getColumnsByOwner(final String owner) {
    final String sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, "
            + " DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT"
            + " FROM ALL_TAB_COLUMNS"
            + " WHERE OWNER = ? ORDER BY COLUMN_ID";

    return jdbcTemplate.query(sql,
            new Object[] { owner },
            new RowMapper<Column>() {
                @Override
                public Column mapRow(final ResultSet res, final int rowNum)
                        throws SQLException {
                    final Column reg = new Column();

                    reg.setColumnName(res.getString("COLUMN_NAME"));
                    //Read other properties
                    reg.setNullable(res.getString("NULLABLE").equals("Y"));
                    return reg;
                }
            });
}

If you need to filter by table simply add " AND TABLE_NAME = ?" to sql and tableName as another parameter.

Hope it helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文