Oracle JDBC prepareStatement使用字符串参数的性能较差

时间:2019-07-03 13:06:27

标签: oracle jdbc

在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();

1 个答案:

答案 0 :(得分:2)

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

我怀疑正在发生某种字符集转换问题,导致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 Screenshot JDBC Screenshot