2

The same select when running in SQLDeveloper or via direct JDBC has completely different performance. The result is just one row.

SQLDeveloper 0.035 seconds SqlDeveloper Screenshot

JDBC 16 seconds. JDBC Screenshot

I'm on the same machine and connected to the same database. The version of the Oracle JDBC driver is ojdbc6 11.2.0.3.

If I change the parameter to filter by the primary key that is long the code runs fast. The two columns are indexed.

Table definition Screenshot

I already tried to change the oracle driver version and the same problem happens.

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();
13
  • It returns just one row. Commented Jul 3, 2019 at 13:58
  • your example string for consultaSQL seems to be truncated. Can you please edit and correct? Can you also post the table definition with the data types? Commented Jul 3, 2019 at 14:28
  • Done @OldProgrammer Commented Jul 3, 2019 at 14:40
  • 1
    What happens if you hard-code the UUID in the consultaSQL string and comment out the setString? The reason I ask is that your two tests are very different: your JDBC example uses a bind variable, while the SQLDeveloper one uses a literal. The Oracle optimizer can definitely treat them different. Bind variables are preferred, but for a quick test, try a literal to see if the JDBC performance is better. Commented Jul 3, 2019 at 15:36
  • 1
    To help you see the plan in use, try adding /* JDBC1 */ /*+ gather_plan_statistics */ after SELECT in the JDBC query and /* SQLDEV1 */ /*+ gather_plan_statistics */ after SELECT in SQL Developer. This will make it easier to find the exact SQL statements run (by searching for SQL text with the JDBC1 or SQLDEV1 respectively) and will help you to get the exact plan used in both places. I think it would be good for you to see the true query plan in use by both. If it turns out that the plan hash is the same for both, that will give a strong indication that it isn't the database. Commented Jul 3, 2019 at 16:03

1 Answer 1

4

This is just a guess, but a good one (I think), and too long for a comment.

I suspect there is some kind of character set conversion issue happening that is causing the Oracle database to interpret your bind value as an NVARCHAR2. The resulting implicit type conversion then prevents Oracle from making use of your index.

Here is a quick example of what I mean:

Set up

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

Get a sample value

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

I got "UCBBTRAB0K8QV1UC8ERA" -- you'll get a different value if you are trying this in your own database.

Simulate what you are doing through 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|
----------------------------------------------------------------------------

So far, so good. The index is being used.

Simulate what I think is happening via 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')

You can see that Oracle is doing an implicit type conversion on that database column. That type conversion function is preventing Oracle from using the index and resulting in a full table scan.

How you can test this

To confirm whether this is truly your problem, modify your JDBC SQL to include a distinctive comment. E.g.,

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 = ?"    

Then, run your JDBC program and look in the database to see what Oracle did with it. First, find your executed statement in the library cache, like this:

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%';

Then, use the sql_id and child_number to view the plan.

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

If the plan indicates a full table scan and the predicate information has a SYS_OP_C2C (or similar) function in it, then you have your explanation.

What you can do about it

Easiest way that should work:

Change your JDBC SQL to this:

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))"    

(From OP: Explain Plan)

SqlDeveloper Screenshot JDBC Screenshot

Sign up to request clarification or add additional context in comments.

5 Comments

So, I was right -- it is an NLS issue. I'm not super-great at those, but if you don't need to completely explain it, you could probably just CREATE INDEX new_index ON EST_SAIDA_ESTOQUE (NLSSORT(UUID,'nls_sort=''BINARY_AI''') and be done with it.
Alternatively, you could try to make UUID an NVARCHAR2 and use setNString() to set its bind value in Java. Doing some quick reading suggests other possibilities as well.
Do you have orai18n.jar in your class path?
This trigger was causing the whole problem. CREATE OR REPLACE TRIGGER erp.TRG_ONLOGON_CHANGE_NLS_SORT AFTER LOGON ON erp.SCHEMA BEGIN execute immediate 'ALTER SESSION SET NLS_SORT = BINARY_AI'; execute immediate 'ALTER SESSION SET NLS_COMP = LINGUISTIC'; END; / I use it for the database not to consider accents and upper and lower case. I do not have this library in my classpath orai18n.jar.
Thank you very much. You are the best.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.