Introduction
I use the Oracle Database since many years. And I use hints. For experiments, but also in production code. There are cases when you know more than the Oracle Database. For example about the cardinality of a data source or the number of result rows to process or the number of expected executions of a statement. Hints are a way to provide additional information, limit the solution space and enable the database to do a better job. That’s a good thing.
Hints Are Instructions
Hints are passed as special comments at a certain position in SQL statements. They are comments, but they are also instructions. They have to be followed. However, there are cases when hints are not applicable. For example when you request the optimizer to use an index when there is no index defined for the underlying table. In such a case the Oracle Database has basically two options. Either throw an error or ignore the invalid instruction and find another solution. The Oracle Database does the latter.
Hint Report
Starting with version 19c you can produce a hint report that reveals unused hints. Here’s an example:
create table t (c1 integer, c2 varchar2(20)); insert into t values (1, 'one'); insert into t values (2, 'two'); select /*+ index(t) */ * from t where c1 > 0; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ select /*+ index(t) */ * from t where c1 > 0 Plan hash value: 1601196873 ------------------------------------------ | Id | Operation | Name | ------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS STORAGE FULL| T | ------------------------------------------ Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 1 (U - Unused (1)) --------------------------------------------------------------------------- 1 - SEL$1 / "T"@"SEL$1" U - index(t)
The hint index(t)
defined on line 4 is valid, but it’s reported as unused on line 25. No wonder. There is no index defined on table t
.
Let’s create an index and rerun the query.
create unique index t_c1_i on t(c1); select /*+ index(t) */ * from t where c1 > 0; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ select /*+ index(t) */ * from t where c1 > 0 Plan hash value: 2704710798 ------------------------------------------------------ | Id | Operation | Name | ------------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| T | | 2 | INDEX RANGE SCAN | T_C1_I | ------------------------------------------------------ Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 1 --------------------------------------------------------------------------- 1 - SEL$1 / "T"@"SEL$1" - index(t)
Now the hint index(t)
defined on line 2 is reported as used on line 24.
Mixing Hints and Comments
What happens if we mix hints and comments? It depends where you place the comment. Let’s look at the next example.
select /*+ index(t) forcing unnecessary index access */ * from t where c1 > 0; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ select /*+ index(t) forcing unnecessary index access */ * from t where c1 > 0 Plan hash value: 2704710798 ------------------------------------------------------ | Id | Operation | Name | ------------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| T | | 2 | INDEX RANGE SCAN | T_C1_I | ------------------------------------------------------ Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 4 (E - Syntax error (3)) --------------------------------------------------------------------------- 1 - SEL$1 E - forcing E - index E - unnecessary 1 - SEL$1 / "T"@"SEL$1" - index(t)
The comment forcing unnecessary index access
on line 1 is interpreted as a series of hints and reported as errors on line 24 to 26. The token access
was not reported. However, the hint index(t)
was reported as used on line 29.
What happens if we move the comment to the beginning?
select /*+ forcing unnecessary index access index(t) */ * from t where c1 > 0; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ select /*+ forcing unnecessary index access index(t) */ * from t where c1 > 0 Plan hash value: 2704710798 ------------------------------------------------------ | Id | Operation | Name | ------------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| T | | 2 | INDEX RANGE SCAN | T_C1_I | ------------------------------------------------------ Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 3 (E - Syntax error (3)) --------------------------------------------------------------------------- 1 - SEL$1 E - forcing E - index E - unnecessary
The same invalid hints are reported as before on line 24 to 26. However, the hint index(t)
was used but not reported as such. This seems to be a limitation of the current hint report in the Oracle Database 21c.
Anyways, it clearly shows that you should not mix comments and hints. Instead you should write it like this:
select /* forcing unnecessary index access */ /*+ index(t) */ * from t where c1 > 0; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ select /* forcing unnecessary index access */ /*+ index(t) */ * from t where c1 > 0 Plan hash value: 2704710798 ------------------------------------------------------ | Id | Operation | Name | ------------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| T | | 2 | INDEX RANGE SCAN | T_C1_I | ------------------------------------------------------ Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 1 --------------------------------------------------------------------------- 1 - SEL$1 / "T"@"SEL$1" - index(t)
Now the hint index(t)
is reported as used. All good, right?
The Problem
I like statically-type languages. Mainly because errors are reported at compile time whenever possible. However, to check hints I need to produce an explain plan. This is possible for a single statement only. This is cumbersome especially when you write code in PL/SQL. As far as I know, there is no option to produce a compile error for invalid hints.
I recently reviewed a system and found a lot of invalid hints. Here are some real-life hints copied from a production code base:
/*+ parallel 4 */
/*+ no_xml_query_rewrite +materialize */
/*+ materialized */
/*+ first rows cardinality (a,10) */
/*+ append nologging */
/*+ le ading(g) u se_nl(g) u se_hash(p, b) */
The last example is a kind of commented out hint series. In this case it’s clearly commented out code. But if you see just a single hint like /*+ le ading(g) */
in the code, you do not know if the space after le
was entered intentionally or by accident.
So, how can we identify invalid hints in our code?
Step 1 – Distinguish Between Comments and Hints
We can configure Oracle’s SQL Developer to show hints in a different color than comments. Here’s the screenshot of an example I showed above:
Go to this GitHub repository and follow the instructions to configure your SQL Developer installation accordingly. See also this blog post for more information about the Arbori code that makes such code highlighting possible.
This step make hints stand out in your code. However, it does not reveal invalid hints.
Step 2 – Install db* CODECOP for SQL Developer
To reveal invalid hints we need a linter. A tool that does some static code analysis. db* CODECOP is such a tool suite. The SQL Developer extension is available for free. It checks the editor content for violations of the Trivadis PL/SQL & SQL Coding Guidelines. Furthermore, db* CODECOP allows you to implement custom guideline checks. The example GitHub repository provides the following four guideline checks regarding hints:
- G-9600: Never define more than one comment with hints.
- G-9601: Never use unknown hints.
- G-9602: Always use the alias name instead of the table name.
- G-9603: Never reference an unknown table/alias.
To install db* CODECOP and these additional custom guideline checks follow the instructions in this GitHub repository.
Finding Wrong Hints With db* CODECOP
I asked my followers on Twitter if this hint is valid:
The result is not really representative. However, 25% thought that /*+ +materialize */
is a valid hint.
Checking the code with db* CODECOP reveals that the hint is invalid and the majority of the poll participants were right.
Verify Result
But is the result of db* CODECOP correct? The following explain plan shows that the hint /*+ +materialize */
is not reported at all. It’s treated as a comment. Another example where the hint report is incomplete.
with e as ( select /*+ +materialize */ * from emp where deptno = 10 ) select * from e; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ with e as ( select /*+ +materialize */ * from emp where deptno = 10 ) select * from e Plan hash value: 3956160932 ------------------------------------------ | Id | Operation | Name | ------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS STORAGE FULL| EMP | ------------------------------------------
Let’s run the same query after removing the extra +
in the hint:
with e as ( select /*+ materialize */ * from emp where deptno = 10 ) select * from e; select * from dbms_xplan.display_cursor(format => 'basic +hint_report'); EXPLAINED SQL STATEMENT: ------------------------ with e as ( select /*+ materialize */ * from emp where deptno = 10 ) select * from e Plan hash value: 3494145522 -------------------------------------------------------------------------------- | Id | Operation | Name | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | TEMP TABLE TRANSFORMATION | | | 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_DFD9DB186_8AAEBD74 | | 3 | TABLE ACCESS STORAGE FULL | EMP | | 4 | VIEW | | | 5 | TABLE ACCESS STORAGE FULL | SYS_TEMP_DFD9DB186_8AAEBD74 | -------------------------------------------------------------------------------- Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 1 --------------------------------------------------------------------------- 2 - SEL$1 - materialize
Now, the materialize
hint has an effect on the execution plan and the hint is reported as used on line 33.
Conclusion
I believe that hints are required for certain use cases. You may have a different opinion. However, if you are using hints in your code you should ensure that they are valid. db* CODECOP can help you to do that. The SQL Developer extension is free. Just use it.
The post Finding Wrong Hints appeared first on Philipp Salvisberg's Blog.