January 3, 2011
A couple of days ago I used a NO_QUERY_TRANSFORMATION hint to permit a query (provided by Martin Berger) to execute, which on the surface appeared to be quite sane, but without hints the query would usually fail to execute. A test case version of the query worked on 11.2.0.1 running on 64 bit Windows on Dec 31, 2010, but suffered from the first known year 2011 bug ( 😉 ), and failed to execute on Jan 1, 2011 in the same database.
A test case version of that sane looking query looks like this:
select owner, view_name from (select v.owner, v.view_name, o.object_type from dba_views v, dba_objects o where v.owner = o.owner AND v.view_name = o.object_name AND o.object_type='VIEW' AND o.status='VALID' AND v.owner ='SYS' ) vv where dbms_metadata.get_ddl(VV.OBJECT_TYPE, vv.view_name, vv.owner) like '%TEST%';
If you think that the DBMS_METADATA.GET_DDL call in the WHERE clause is at fault, you might try this query instead:
select owner, view_name from (SELECT dbms_metadata.get_ddl('VIEW', vv.view_name, vv.owner) DDL, OWNER, VIEW_NAME FROM (select v.owner, v.view_name, o.object_type from dba_views v, dba_objects o where v.owner = o.owner AND v.view_name = o.object_name AND o.object_type='VIEW' AND o.status='VALID' AND v.owner ='SYS' ) vv ) WHERE DDL like '%TEST%';
So, the NO_QUERY_TRANSFORMATION hint was not ignored when the above queries were hinted, and the hint allowed both of the above queries to execute when that hint was added immediately after the SELECT. Just to make certain that there is no magic involved with this hint, let’s take a look at the description from the Oracle Database 11.2 documentation:
“The NO_QUERY_TRANSFORMATION hint instructs the optimizer to skip all query transformations, including but not limited to OR-expansion, view merging, subquery unnesting, star transformation, and materialized view rewrite. For example:
SELECT /*+ NO_QUERY_TRANSFORMATION */ employee_id, last_name FROM (SELECT * FROM employees e) v WHERE v.last_name = 'Smith'; "
I think that the above explanation is easy to understand – essentially: leave my query alone!
So, what led to this blog article? I saw a statement in the “Pro Oracle SQL” book on page 329 that I wanted to verify since I had never used the ANY keyword in a SQL statement:
“There isn’t much to say about the =ANY version [of the SQL statement] since it is merely an alternate way of writing IN.”
I set up a simple test case script with two tables to see if I could demonstrate to myself that a query with the ANY keyword is handled exactly the same as a query using the IN syntax:
CREATE TABLE T1 AS SELECT ROWNUM C1, LPAD('A',100,'A') PADDING FROM DUAL CONNECT BY LEVEL<=100; CREATE TABLE T2 AS SELECT ROWNUM C0, TRUNC((MOD(ROWNUM-1,80)+1)*1.2) C1, LPAD('A',100,'A') PADDING FROM DUAL CONNECT BY LEVEL<=1000; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1') EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T2')
Let’s assume that table T1 is a list of items in a hardware store, and table T2 is a list of items that were purchased in the last month. We just need a quick way to determine the list of items that were purchased in the last month (we might also need to select another column, such as the product description from table T1, thus the need to access both tables T1 and T2). So, we put together the following two SQL statements to see if the ANY syntax really is equivalent to the IN syntax:
SELECT T1.C1 FROM T1 WHERE T1.C1 IN ( SELECT T2.C1 FROM T2); SELECT T1.C1 FROM T1 WHERE T1.C1 = ANY ( SELECT T2.C1 FROM T2);
First, let’s check the execution plans:
SET AUTOTRACE TRACEONLY EXPLAIN SELECT T1.C1 FROM T1 WHERE T1.C1 IN ( SELECT T2.C1 FROM T2); Execution Plan ---------------------------------------------------------- Plan hash value: 1713220790 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 80 | 480 | 11 (10)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 80 | 480 | 11 (10)| 00:00:01 | | 2 | TABLE ACCESS FULL| T1 | 100 | 300 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| T2 | 1000 | 3000 | 7 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T1"."C1"="T2"."C1") - SELECT T1.C1 FROM T1 WHERE T1.C1 = ANY ( SELECT T2.C1 FROM T2); Execution Plan ---------------------------------------------------------- Plan hash value: 1713220790 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 80 | 480 | 11 (10)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 80 | 480 | 11 (10)| 00:00:01 | | 2 | TABLE ACCESS FULL| T1 | 100 | 300 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| T2 | 1000 | 3000 | 7 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T1"."C1"="T2"."C1")
The execution plans appear to be the same. But wait, through the magic of automatic query transformations, Oracle’s optimizer might have actually rewritten both SQL statements so that the actual SQL executed for both queries is identical. I recall experiencing performance problems in Oracle Database 8.1.7.x with certain IN type queries, while the equivalent EXISTS queries were fast. Let’s use the magic NO_QUERY_TRANSFORMATION hint that allowed the SQL statement at the start of this blog article to execute, so that we may prevent the optimizer from rewriting the SQL statement. That hint should help indicate whether or not the SQL statement using the ANY syntax really is equivalent to the query using the IN syntax. Note that this test is performed on Oracle Database 11.2.0.1 and you might receive different results on different Oracle release versions. The hinted test queries and their execution plans:
SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1 FROM T1 WHERE T1.C1 IN ( SELECT T2.C1 FROM T2); Execution Plan ---------------------------------------------------------- Plan hash value: 895956251 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 3 | 103 (0)| 00:00:02 | |* 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL| T1 | 100 | 300 | 3 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| T2 | 2 | 6 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter( EXISTS (SELECT 0 FROM "T2" "T2" WHERE "T2"."C1"=:B1)) 3 - filter("T2"."C1"=:B1) - SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1 FROM T1 WHERE T1.C1 = ANY ( SELECT T2.C1 FROM T2); Execution Plan ---------------------------------------------------------- Plan hash value: 895956251 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 3 | 103 (0)| 00:00:02 | |* 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL| T1 | 100 | 300 | 3 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| T2 | 2 | 6 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter( EXISTS (SELECT 0 FROM "T2" "T2" WHERE "T2"."C1"=:B1)) 3 - filter("T2"."C1"=:B1)
In you look closely, you will see that the execution plans changed from their unhinted versions, and both of the hinted execution plans are identical. But wait! Take a close look at the Predicate Information section of the execution plan, specifically the line for plan ID 1. Where did that EXISTS entry come from – the hint should have told Oracle’s optimizer to not transform the query? It appears that both queries were transformed into EXISTS queries. Let’s take a look at the 10053 trace file for a slightly modified version of the queries (so that we will force a hard parse):
ALTER SESSION SET TRACEFILE_IDENTIFIER='NO_QUERY_TRANSFORM'; ALTER SESSION SET EVENTS '10053 TRACE NAME CONTEXT FOREVER, LEVEL 1'; SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1, 1 FROM T1 WHERE T1.C1 IN ( SELECT T2.C1 FROM T2); SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1, 1 FROM T1 WHERE T1.C1 = ANY ( SELECT T2.C1 FROM T2); ALTER SESSION SET EVENTS '10053 TRACE NAME CONTEXT OFF';
Taking a look in the trace file:
Final query after transformations:******* UNPARSED QUERY IS ******* SELECT "SYS_ALIAS_1"."C1" "C1",1 "1" FROM "TESTUSER"."T1" "SYS_ALIAS_1" WHERE EXISTS (SELECT 0 FROM "TESTUSER"."T2" "T2" WHERE "T2"."C1"="SYS_ALIAS_1"."C1") ... sql=SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1, 1 FROM T1 WHERE T1.C1 IN ( SELECT T2.C1 FROM T2) ----- Explain Plan Dump ----- ----- Plan Table ----- ============ Plan Table ============ --------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | --------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 103 | | | 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL | T1 | 100 | 300 | 3 | 00:00:01 | | 3 | TABLE ACCESS FULL | T2 | 2 | 6 | 2 | 00:00:01 | --------------------------------------+-----------------------------------+ Predicate Information: ---------------------- 1 - filter( IS NOT NULL) 3 - filter("T2"."C1"=:B1) ---------------------------------------------------------------------------------- Final query after transformations:******* UNPARSED QUERY IS ******* SELECT "SYS_ALIAS_1"."C1" "C1",1 "1" FROM "TESTUSER"."T1" "SYS_ALIAS_1" WHERE EXISTS (SELECT 0 FROM "TESTUSER"."T2" "T2" WHERE "T2"."C1"="SYS_ALIAS_1"."C1") ... sql=SELECT /*+ NO_QUERY_TRANSFORMATION */ T1.C1, 1 FROM T1 WHERE T1.C1 = ANY ( SELECT T2.C1 FROM T2) ----- Explain Plan Dump ----- ----- Plan Table ----- ============ Plan Table ============ --------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | --------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 103 | | | 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL | T1 | 100 | 300 | 3 | 00:00:01 | | 3 | TABLE ACCESS FULL | T2 | 2 | 6 | 2 | 00:00:01 | --------------------------------------+-----------------------------------+ Predicate Information: ---------------------- 1 - filter( IS NOT NULL) 3 - filter("T2"."C1"=:B1)
From the above, we are able to see that despite the NO_QUERY_TRANSFORMATION hint, both queries were written identically to use an EXISTS clause. Maybe the NO_QUERY_TRANSFORMATION hint was ignored? From the 10053 trace file:
End of Optimizer State Dump Dumping Hints ============= atom_hint=(@=000007FFA41C2E00 err=0 resol=0 used=1 token=986 org=1 lvl=1 txt=NO_QUERY_TRANSFORMATION ()) ====================== END SQL Statement Dump ======================
The above shows that the hint did not result in an error, and was used. Well, maybe we should just say that the NO_QUERY_TRANSFORMATION hint was almost ignored.
Charles,
It could be quite entertaining to think about what Oracle would do with ANSI SQL if you include the /*+ no_query_transformation */ hint. If it meant what it appears to mean then Oracle wouldn’t be able to optimise it at all because ANSI has to be transformed BEFORE optimisation.
I’ve always assumed that the hint meant “no cost based query transformation” – which would allow some “heuristic” transformations and “canonical” transformations to take place. (Canonical probably isn’t the right word, but I was thinking of the IN / EXISTS rewrite that ought to have nothing to do with cost, and simply acted to standardise the text).
Jonathan,
Thank you for the comments. I am probably remembering incorrectly, but I thought that the decision to convert an IN type query to an EXISTS type query was a cost-based decision starting with Oracle Database 10.1 – but maybe I am confusing that transformation decision with the transformation decision to convert an IN type query into an inline view.
I mentioned in the blog article that Oracle Database 8.1.7.x did not automatically transform IN type queries into EXISTS type queries, so if we were to set OPTIMIZER_FEATURES_ENABLE to 8.1.7, we should see that same behavior. Here is a test that was performed on Oracle Database 10.2.0.2:
The result? The transformation was *not* made from an IN type query to an EXISTS type query unless the NO_QUERY_TRANSFORMATION hint was provided. From looking at the 10053 trace file, it appears that the optimizer did not even consider transforming the query to an EXISTS type query unless the NO_QUERY_TRANSFORMATION hint was provided. Here is partial output from the 10053 trace file:
OPTIMIZER_FEATURES_ENABLE=’8.1.7′, unhinted:
OPTIMIZER_FEATURES_ENABLE=’8.1.7′, hinted with NO_QUERY_TRANSFORMATION:
——————————–
OPTIMIZER_FEATURES_ENABLE=’10.2.0.2′, unhinted:
OPTIMIZER_FEATURES_ENABLE=’10.2.0.2′, hinted with NO_QUERY_TRANSFORMATION:
Other than the differences in cardinality estimates and cost estimates, the execution plans with the NO_QUERY_TRANSFORMATION hint appear to be identical, regardless of the OPTIMIZER_FEATURES_ENABLE value. I am not expecting an explanation of why this happens, I just thought that it would be interesting to post these findings.
I just noticed something as a result of what Kerry Osborne wrote into chapter 11 (page 342) of the SQL book. Autotrace will show in the Predicate Information section more detailed information, for example “1 – filter( EXISTS (SELECT 0 FROM “T2” “T2” WHERE “T2”.”C1″=:B1))“, while the 10053 trace file and DBMS_XPLAN.DISPLAY_CURSOR function display less detail, for example “1 – filter( IS NOT NULL)“.
For example, if we rerun the script from my previous comment with AUTOTRACE enabled:
We will see the more extensive detail provided by AUTOTRACE:
OPTIMIZER_FEATURES_ENABLE=’8.1.7′, unhinted:
OPTIMIZER_FEATURES_ENABLE=’8.1.7′, hinted with NO_QUERY_TRANSFORMATION:
OPTIMIZER_FEATURES_ENABLE=’10.2.0.2′, unhinted:
OPTIMIZER_FEATURES_ENABLE=’10.2.0.2′, hinted with NO_QUERY_TRANSFORMATION:
Of course it is possible to pull the execution plans from the server’s memory to compare with something like this:
I guess that it is good to try to learn something new every day.
Interesting idea Jonathan. I’m surprised you haven’t tried that yet. 😉 I gave a quick try and managed to basically hang my session on the first shot.
Here’s a little output from 11.2.0.2 (Exadata)
I Wolfgang’ed the statement and it was indeed transformed, to this:
So it doesn’t look like NO_QUERY_TRANSFORMATION really means “NO” transformations.
Kerry,
Thank you for stopping by, and for performing your test on Exadata. I am interested in seeing the “Final query after transformation” section of a 10053 trace for the unhinted version of the query. I am curious to know if the final query after transformation is written any differently than it would be on a non-Exadata implementation.
By the way, I am impressed with chapter 11 of the “Pro Oracle SQL” book – very well written.
The non-hinted version got transformed to standard Oracle join syntax (as expected):
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT(*) “COUNT(*)” FROM “KSO”.”LITTLE_SKEW” “A”,”KSO”.”LITTLE_SKEW” “B” WHERE “A”.”PK_COL”=”B”.”PK_COL”(+)
Basically the hinted version ran (although it never returned) but the trace file looked like the optimizer had pretty much lost it’s mind. There were many transformations that were apparently still evaluated even with the hint.
Thanks for the kind words on the book. I appreciate you taking the time to read it and comment. I haven’t actually had time to read it all the way through yet myself. 😉 Hopefully you’ll like the last chapter as well (Plan Stability and Control) as it covers a topic that I am very interested in.
Hi charles,
Your posts on Oracle are pretty good. But, my question is something else.
How are you able to embed your code into a block? I’d love to do the same. Could you please explain or give me the source?
Thanks,
Anil
The specific WordPress theme that I selected “Freshy by Jude” includes in its cascading style sheet (CSS) the description for the code blocks. The Freshy theme was one of only a small number that supported horizontally scrolling code sections, which are definitely needed in a large number of my blog posts. I then paid WordPress for the option (roughly $50 USD per year) to modify the cascading style sheet so that I could fine tune the appearance of this blog (you might be able to do the same, and then borrow the code section formatting from the Freshy theme).
You can see why my blog was modified by looking at the comments in these blog articles:
https://hoopercharles.wordpress.com/2009/12/01/sql-plan_hash_value-changes-for-the-same-sql-statement/
https://hoopercharles.wordpress.com/2010/04/08/true-or-false-autotrace/
Charles,
I tried to use NO_QUERY_TRANSFORMATION and Hint is ignored in CTAS statement. Standalone SELECT … works perfectly in 16 min’s however with CTAS the execution plan changes and takes 4hrs+ to finish. How can I keep the same execution plan with or without CTAS in 11.2.0.3 on RH Linux 5.5.
Thanks