在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();
答案 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”-如果您在自己的数据库中尝试此操作,则会得到不同的值。
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| ----------------------------------------------------------------------------
到目前为止,太好了。正在使用索引。
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_id
和child_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))"