ORACLE数据库中执行计划出现INTERNAL_FUNCTION必定是隐式转换吗?

ORACLE数据库中,咱们会使用一些SQL语句找出存在隐式转换的问题SQL,其中网上流传的一个SQL语句以下,查询V$SQL_PLAN的字段FILTER_PREDICATES中是否存在INTERNAL_FUNCTION sql

 

SELECT
     SQL_ID,
     PLAN_HASH_VALUE
FROM
     V$SQL_PLAN X
WHERE
     X.FILTER_PREDICATES LIKE '%INTERNAL_FUNCTION%'
GROUP BY
     SQL_ID,
     PLAN_HASH_VALUE;

 

 

可是笔者测试验证发现,有时候,执行计划中出现INTERNAL_FUNCTION并不必定表明出现了隐式数据类型转换,下面咱们结合这篇博客What the heck is the INTERNAL_FUNCTION in execution plan predicate section?来说述一下执行计划谓词部分中的INTERNAL_FUNCTION究竟是什么?这篇博客没有打算直接翻译这篇文章,而是想结合本身的理解,来简单讲述一下INTERNAL_FUNCTION。其实官方文档对INTERNAL_FUNCTION的介绍很是少,最多见的理解,INTERNAL_FUNCTION这种特殊函数用于执行隐式数据类型转换(implicit datatype conversion),可能来自官方文档https://docs.oracle.com/cd/E11882_01/server.112/e25523/part_avail.htm#sthref141 。可是这个说法,事实上仅仅部分正确,而不是所有的事实。事实上,ORACLE中找不到INTERNAL_FUNCTION这个函数,经过V$SQLFN_METADATA视图根本找不到INTERNAL_FUNCTION这个对象。数据库

 

COL sqlfn_descr HEAD DESCRIPTION FOR A100 WORD_WRAP 
COL sqlfn_name  HEAD NAME FOR A30 
 
 
SELECT 
     func_id 
   , name sqlfn_name 
   , offloadable 
 --  , usage 
   , minargs 
   , maxargs 
     -- this is just to avoid clutter on screen 
   , CASE WHEN name != descr THEN descr ELSE null END sqlfn_descr  
 FROM 
     v$sqlfn_metadata  
 WHERE  
     UPPER(name) LIKE UPPER('%&1%') 
 /

 

通常而言,咱们在执行计划的的谓词部分发现出现INTERNAL_FUNCTION,那么可能意味着出现了隐式类型转换(implicit data type conversion),下面我先简单构造一个例子,缓存

 

SQL> CREATE TABLE t(a VARCHAR2(20), b DATE);
 
Table created.
 
SQL> INSERT INTO t VALUES( TO_CHAR(sysdate), sysdate) ;
 
1 row created.
 
SQL> commit;
 
Commit complete.

 

以下所示,这个SQL会出现隐式数据类型转换(implicit datatype conversionsession

 

SQL> SELECT * FROM t WHERE a = b;
 
no rows selected
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  4ptcbny27y9b0, child number 0
-------------------------------------
SELECT * FROM t WHERE a = b
 
Plan hash value: 1601196873
 
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |     2 (100)|          |
|*  1 |  TABLE ACCESS FULL| T    |     1 |    21 |     2   (0)| 00:00:01 |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("B"=INTERNAL_FUNCTION("A"))
 
Note
-----
   - dynamic sampling used for this statement
 
 
22 rows selected.

 

clip_image001

 

 

经过执行计划,咱们看到ORACLE为了可以比较两个不一样数据类型(字段AB之间的比较),强制在字段A上加了一个数据类型转换函数,在ORACLE内部,运算从WHERE a=b 转换为WHERE  TO_DATE(a=b, 这也是为何执行计划中出现INTERNAL_FUNCTION的缘由-从实际的二进制执行计划生成可读性的执行计划的代码没法将内部操做码转换为相应的适合人们容易理解的函数名称,所以默认使用INTERNAL_FUNCTION字符串取而代之显示。 英文原文以下,能够对比理解(若是以为翻译的很差的话)oracle

 

What happens here is that Oracle is forced to (implicitly) add a datatype conversion function around column A, to be able to physically compare two different datatypes. Internally Oracle is not running a comparison <strong>"WHERE a = b"</strong> anymore, but rather something like <strong>"WHERE TO_DATE(a) = b"</strong>. This is one of the reasons why the INTERNAL_FUNCTION shows up the code generating the human-readable execution plan from the actual binary execution plan is not able to convert the internal opcode to a corresponding human-readable function name, thus shows a default INTERNAL_FUNCTION string there instead.app

 

 

Un-unparseable Complex Expressionside

 

执行计划中出现INTERNAL_FUNCTION,还有一种状况是由于不可分割的复杂表达式(Un-unparseable Complex Expressions,下面经过一个例子来讲明一下函数

 

SQL> drop table t purge;
 
Table dropped.
 
SQL> CREATE TABLE t AS SELECT * FROM dba_objects;
 
Table created.
 
SQL> SELECT COUNT(*) FROM t WHERE owner = 'SYS' OR owner = 'SYSTEM';
 
  COUNT(*)
----------
     23851
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  77xzyugx5q3kf, child number 0
-------------------------------------
SELECT COUNT(*) FROM t WHERE owner = 'SYS' OR owner = 'SYSTEM'
 
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   108 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    17 |            |          |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  2 |   TABLE ACCESS FULL| T    | 22494 |   373K|   108   (7)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("OWNER"='SYS' OR "OWNER"='SYSTEM'))
 
Note
-----
   - dynamic sampling used for this statement
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

 

如今,咱们让谓词稍微复杂一点,在查询条件中添加另外一个OR,但这是针对另外一列object_id的查询条件,以下所示:测试

 

SQL> SELECT COUNT(*) FROM t WHERE owner = 'SYS' OR owner = 'SYSTEM' OR object_id = 123;
 
  COUNT(*)
----------
     23851
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  9vh8b6ku8sd1t, child number 0
-------------------------------------
SELECT COUNT(*) FROM t WHERE owner = 'SYS' OR owner = 'SYSTEM' OR
object_id = 123
 
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   111 (100)|          |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T    | 22494 |   659K|   111  (10)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter((INTERNAL_FUNCTION("OWNER") OR "OBJECT_ID"=123))
 
Note
-----
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   - dynamic sampling used for this statement
 
 
24 rows selected.

 

clip_image002

 

修改WHERE查询条件后,OWNER表上的两个查询条件消失了,由INTERNAL_FUNCTION替换了,接下来,让咱们用IN运算符,而不是OR,可是上面SQL是不一样字段之间的OR,咱们须要修改一下SQL语句ui

 

SQL> SELECT COUNT(*) FROM t WHERE owner IN ('SYS','SYSTEM','SCOTT') AND object_type = 'TABLE';
 
  COUNT(*)
----------
       896
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
 
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  gcqgrmtna9g1u, child number 0
-------------------------------------
SELECT COUNT(*) FROM t WHERE owner IN ('SYS','SYSTEM','SCOTT') AND
object_type = 'TABLE'
 
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   111 (100)|          |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE    |      |     1 |    16 |            |          |
|*  2 |   TABLE ACCESS FULL| T    |   894 | 14304 |   111  (10)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("OBJECT_TYPE"='TABLE' AND INTERNAL_FUNCTION("OWNER")))
 
 
20 rows selected.

 

很不幸,上面执行计划中谓词部分依然出现了INTERNAL_FUNCTION,咱们在逻辑上简化一下,只搜寻同一个字段上的三个值:

 

SQL> SELECT COUNT(*) FROM t WHERE owner IN ('SYS','SYSTEM','SCOTT');
 
  COUNT(*)
----------
     23857
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  2qazbqj67y17s, child number 0
-------------------------------------
SELECT COUNT(*) FROM t WHERE owner IN ('SYS','SYSTEM','SCOTT')
 
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   111 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |     7 |            |          |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  2 |   TABLE ACCESS FULL| T    | 24133 |   164K|   111  (10)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("OWNER"='SCOTT' OR "OWNER"='SYS' OR "OWNER"='SYSTEM'))
 
 
19 rows selected.

 

如上所示,它确实生效了,ORACLE已将IN谓词转换为(或至少在执行计划中显示了)了一堆OR-ed条件(针对同一列)

你可能已经看到了前面的例子的执行计划输出内容 DBMS_XPLAN.DISPLAY_CURSOR没法解释在单个执行计划步骤中应用的复杂的复合谓词,其中包括多个不一样的列,而且至少其中一个列具备多个要检查的值(例如列表中或OR-ed谓词)

 

DISPLAY_CURSOR从何处获取数据并进行解释呢?

 

DBMS_XPLAN.DISPLAY_CURSORV$SQL_PLAN获取其执行计划的相关数据,谓词部分来自ACCESS_PREDICATESFILTER_PREDICATES列。可是当我直接查询V$SQL_PLAN时,我仍然看到相同的问题:

 SQL> SELECT id, filter_predicates FROM v$sql_plan WHERE sql_id = 'gcqgrmtna9g1u';

 

        ID FILTER_PREDICATES

---------- ------------------------------------------------------------

         0

         1

         2 (INTERNAL_FUNCTION("OWNER") AND "OBJECT_TYPE"='TABLE')

 

你可能已经注意到,上面的原始ORed条件周围也有括号(),这在9i中,意味着谓词周围的二进制执行计划中存在没法解释的内部函数,可是在这种状况下(如10g +支持internal_function命名),不该出现空白的函数名称……不肯定为何会出现这种状况,但这对本篇文章来讲太深刻了。

V$SQL_PLAN视图自己访问库高速缓存(library cache)中的实际二进制子游标(在使用了适当的latches/pins/mutexe以后)并对其进行解析。为何用这样的术语-其实并非根据人类容易理解的输入并将其转换为计算机可理解的二进制格式。悄悄相反 V$SQL_PLAN访问游标中的二进制执行计划的内存结构,并将其转换为人类可读的执行计划输出。甚至还有一个参数控制此V$SQL_PLAN的行为,若是将其设置为false,则ACCESS_PREDICATESFILTER_PREDICATES列将为空:

这段真很差翻译(有可能翻译不当),参考英文原文以下:

The V$SQL_PLAN view itself accesses the actual binary child cursor in library cache (after taking appropriate latches/pins/mutexes) and UNPARSES it. Why such term well isnt parsing something that takes a human readable input and translates it into computer-understandable binary format. Thus unparsing is the opposite V$SQL_PLAN accesses the cursors binary execution plan memory structure and translates it to human-readable execution plan output. Theres even a parameter controlling this V$SQL_PLAN behavior, if its set to false, the ACCESS_PREDICATES and FILTER_PREDICATES columns will be empty there:

 

SQL> @pd unparse
Show all parameters and session values from x$ksppi/x$ksppcv...
 
NAME                             VALUE                                      DESCRIPTION
----------------------------- --------- -----------------------------------------------
_cursor_plan_unparse_enabled      TRUE          enables/disables using unparse to build
                                                                  projection/predicates

 

顺便说一句,为何我老是说二进制执行计划并用双引号括起来? 这是由于我想强调,ORACLE的实际执行计划并不像咱们在屏幕上看到的输出的文本那样,这些输出的执行计划只是为了在troubleshooting的时候,更好的适应人类的阅读习惯而生成的文本(这里其实就是说转换成了符合人类阅读系统的文本),执行计划也不是真正的可执行二进制文件(如oracle.exe中同样),也没有直接反馈给CPU执行。 库缓存子游标中的物理执行计划(physical execution plan)是一堆操做码(a bunch of opcodes),object_id和指针,用于定义行源执行的层次结构和顺序。 SQL执行引擎去循环遍历这些操做码,对其进行解码,而后知道下一步该作什么(要调用哪一个rowsource函数)。

所以,如上所述,某些具备复杂AND / OR条件的谓词被DBMS_XPLAN显示为INTERNAL_FUNCTION()。DISPLAY_CURSORV$SQL_PLAN由于它们也没法彻底解码(解析)执行计划信息。

Using the good old EXPLAIN PLAN

 

不过有个好消息! 旧的EXPLAIN PLAN命令可以正确的解析这些复杂谓词(固然仅仅是其中一部分),当EXPLAIN PLAN以一种特殊、更加仪器化的方式(more instrumented way)解析给定的SQL语句时,它显然手头有更多信息(而且它还使用了更多的内存)。或者可能只是谁写了V$SQL_PLAN,没有编写一段代码来解析更复杂的谓词:),以下所示:

 

SQL> EXPLAIN PLAN FOR 
  2  SELECT COUNT(*) FROM t WHERE owner IN ('SYS','SYSTEM','SCOTT') AND object_type = 'TABLE';
 
Explained.
 
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    16 |   111  (10)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |    16 |            |          |
|*  2 |   TABLE ACCESS FULL| T    |   894 | 14304 |   111  (10)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
---------------------------------------------------
 
   2 - filter("OBJECT_TYPE"='TABLE' AND ("OWNER"='SCOTT' OR
              "OWNER"='SYS' OR "OWNER"='SYSTEM'))
 
15 rows selected.
 
SQL> 

这真是一个奇迹,INTERNAL_FUNCTION消失不见了,全部的谓词都正确的显示了,EXPLAIN PLAN命令在这里很是有用。

 

所以,尽管我一般不使用EXPLAIN PLAN命令,由于EXPLAIN PLAN输出的执行计划可能会骗你,可是,每当我在DISPLAY_CURSOR/V$SQL_PLAN/SQL Monitor输出中看到INTERNAL_FUNCTION时,我都会运行EXPLAIN PLAN命令执行同一个SQL,但愿快速找出其中的谓词INTERNAL_FUNCTION表明的真正意义。

 

 

参考资料:

 

https://blog.tanelpoder.com/2013/01/16/what-the-heck-is-the-internal_function-in-execution-plan-predicate-section/

https://docs.oracle.com/cd/E11882_01/server.112/e25523/part_avail.htm#sthref141

相关文章
相关标签/搜索