【问题标题】:Oracle JDBC preparedStatement bad performance with string parameter带有字符串参数的 Oracle JDBC PreparedStatement 性能不佳
【发布时间】:2019-11-14 04:28:53
【问题描述】:

在 SQLDeveloper 中运行或通过直接 JDBC 运行时,相同的选择具有完全不同的性能。结果只有一行。

SQLDeveloper 0.035 秒 SqlDeveloper Screenshot

JDBC 16 秒。 JDBC Screenshot

我在同一台机器上并连接到同一个数据库。 Oracle JDBC驱动的版本是ojdbc6 11.2.0.3。

如果我将参数更改为按长主键进行过滤,则代码运行速度很快。这两列都被索引了。

表定义Screenshot

我已经尝试过更改oracle驱动版本,同样的问题发生了。

Connection conn = DriverManager.getConnection(url, "xxxx", "xxxx");
PreparedStatement preparedStatement = null;
ResultSet rs = null;

try {
System.out.println("Connected to database");

String consultaSQL = "SELECT SE.ID_SAIDA_ESTOQUE, SE.DATA_ULTIMA_ATUALIZACAO,  "
                + "SE.SITUACAO_VENDA, SE.ID_CLIENTE, SE.ID_ENTRADA_ESTOQUE_TROCA, SE.UUID, SE.ID_OPERACAO_MOVIMENTO, SE.ID_SETOR_ESTOQUE, SE.ID_EMPRESA, SE.ID_TERMINAL "
                + "FROM EST_SAIDA_ESTOQUE SE WHERE SE.UUID = ?"    
preparedStatement = conn.prepareStatement(consultaSQL);
preparedStatement.setQueryTimeout(40);
preparedStatement.setString(1, "000001c8-38d5-47c8-8e19-980c0c66e183");
//preparedStatement.setLong(1, 230998);
rs = preparedStatement.executeQuery();

【问题讨论】:

  • 它只返回一行。
  • 您的consultaSQL 示例字符串似乎被截断了。你能编辑和更正吗?您还可以发布带有数据类型的表定义吗?
  • 完成@OldProgrammer
  • 如果您在consultaSQL 字符串中硬编码UUID 并注释掉setString 会发生什么?我问的原因是您的两个测试非常不同:您的 JDBC 示例使用绑定变量,而 SQLDeveloper 示例使用文字。 Oracle 优化器绝对可以区别对待它们。绑定变量是首选,但为了快速测试,请尝试使用文字来查看 JDBC 性能是否更好。
  • 为了帮助您查看正在使用的计划,请尝试在 JDBC 查询中在 SELECT 之后添加 /* JDBC1 */ /*+ gather_plan_statistics */,在 SQL Developer 中在 SELECT 之后添加 /* SQLDEV1 */ /*+ gather_plan_statistics */。这将更容易找到运行的确切 SQL 语句(通过分别使用 JDBC1SQLDEV1 搜索 SQL 文本),并将帮助您获得在这两个地方使用的确切计划。我认为如果你能看到两者都在使用真正的查询计划,那将是一件好事。如果事实证明两者的计划哈希相同,则强烈表明它不是数据库。

标签: oracle jdbc


【解决方案1】:

这只是一个猜测,但很好(我认为),而且评论太长了。

我怀疑发生了某种字符集转换问题,导致 Oracle 数据库将您的绑定值解释为 NVARCHAR2。由此产生的隐式类型转换会阻止 Oracle 使用您的索引。

这是我的意思的一个简单示例:

设置

CREATE TABLE matt1 ( a varchar2(30) );

INSERT INTO matt1  SELECT dbms_random.string('X',20) FROM   dual CONNECT BY ROWNUM <= 50000;

COMMIT;

CREATE INDEX matt1_n1 ON matt1 (a);

获取样本值

SELECT * FROM matt1 order by dbms_random.value fetch first 1 row only;

我得到了“UCBBTRAB0K8QV1UC8ERA”——如果你在自己的数据库中尝试这个,你会得到不同的值。

通过 SQL*Developer 模拟你正在做的事情:

EXPLAIN PLAN SET STATEMENT_ID='MM1' FOR
SELECT * FROM matt1 WHERE a = 'UCBBTRAB0K8QV1UC8ERA';

SELECT * 
FROM   TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE','MM1','ADVANCED'));
Plan hash value: 2474448389

----------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time    |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |          |     1 |    17 |     1   (0)| 00:00:01|
|*  1 |  INDEX RANGE SCAN| MATT1_N1 |     1 |    17 |     1   (0)| 00:00:01|
----------------------------------------------------------------------------

到目前为止,一切都很好。该索引正在使用中。

通过 JDBC 模拟我认为正在发生的事情

EXPLAIN PLAN SET STATEMENT_ID='MM2' FOR SELECT * FROM matt1 WHERE a =
N'UCBBTRAB0K8QV1UC8ERA';

SELECT *  FROM  
TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE','MM2','ADVANCED'));
Plan hash value: 1348340248

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |     3 |    51 |    50  (10)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| MATT1 |     3 |    51 |    50  (10)| 00:00:01 |
---------------------------------------------------------------------------

... lots of stuff omitted...

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(SYS_OP_C2C("A")=U'UCBBTRAB0K8QV1UC8ERA')

您可以看到 Oracle 正在对该数据库列进行隐式类型转换。该类型转换功能阻止 Oracle 使用索引并导致全表扫描。

你如何测试这个

要确认这是否真的是您的问题,请修改您的 JDBC SQL 以包含一个独特的注释。例如,

String consultaSQL = "SELECT /* THIS_IS_MY_JDBC_STATEMENT_1 */ SE.ID_SAIDA_ESTOQUE, SE.DATA_ULTIMA_ATUALIZACAO,  "
                + "SE.SITUACAO_VENDA, SE.ID_CLIENTE, SE.ID_ENTRADA_ESTOQUE_TROCA, SE.UUID, SE.ID_OPERACAO_MOVIMENTO, SE.ID_SETOR_ESTOQUE, SE.ID_EMPRESA, SE.ID_TERMINAL "
                + "FROM EST_SAIDA_ESTOQUE SE WHERE SE.UUID = ?"    

然后,运行 JDBC 程序并查看数据库,看看 Oracle 用它做了什么。首先,在库缓存中找到您执行的语句,如下所示:

select sql_id, child_number from gv$sql where sql_text like 
'%THIS_IS_MY_JDBC_STATEMENT_1%' and sql_text not like '%THIS_ONE%';

然后,使用sql_idchild_number 查看计划。

SELECT *
FROM   TABLE (DBMS_XPLAN.display_cursor ('gyzm0fq259h5d' /* sql_id */,
                                         0 /* child_number */,
                                         'ADVANCED LAST'));

如果计划指示全表扫描并且谓词信息中包含SYS_OP_C2C(或类似)函数,那么您有自己的解释。

你能做些什么

应该工作的最简单方法:

将您的 JDBC SQL 更改为:

String consultaSQL = "SELECT /* THIS_IS_MY_JDBC_STATEMENT_1 */ SE.ID_SAIDA_ESTOQUE, SE.DATA_ULTIMA_ATUALIZACAO,  "
                + "SE.SITUACAO_VENDA, SE.ID_CLIENTE, SE.ID_ENTRADA_ESTOQUE_TROCA, SE.UUID, SE.ID_OPERACAO_MOVIMENTO, SE.ID_SETOR_ESTOQUE, SE.ID_EMPRESA, SE.ID_TERMINAL "
                + "FROM EST_SAIDA_ESTOQUE SE WHERE SE.UUID = CAST(? AS VARCHAR2(255 CHAR))"    

(来自 OP:解释计划)

SqlDeveloper JDBC

【讨论】:

  • 所以,我是对的——这是一个 NLS 问题。我不是很擅长这些,但如果你不需要完全解释它,你可能只需要CREATE INDEX new_index ON EST_SAIDA_ESTOQUE (NLSSORT(UUID,'nls_sort=''BINARY_AI''') 就可以了。
  • 或者,您可以尝试将UUID 设为NVARCHAR2 并使用setNString() 在Java 中设置其绑定值。做一些快速阅读也暗示了其他可能性。
  • 你的类路径中有 orai18n.jar 吗?
  • 这个触发器导致了整个问题。登录后创建或替换触发器 erp.TRG_ONLOGON_CHANGE_NLS_SORT erp.SCHEMA BEGIN 立即执行 'ALTER SESSION SET NLS_SORT = BINARY_AI';立即执行“ALTER SESSION SET NLS_COMP = LINGUISTIC”;结尾; /我用它为数据库不考虑重音和大小写。我的类路径 orai18n.jar 中没有这个库。
  • 非常感谢。你是最棒的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2011-12-02
  • 2013-05-02
  • 1970-01-01
  • 2021-11-12
  • 2012-09-26
  • 2017-04-15
相关资源
最近更新 更多