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

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

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.

通过执行计划,我们看到ORACLE为了能够比较两个不同数据类型(字段A与B之间的比较),强制在字段A上加了一个数据类型转换函数,在ORACLE内部,运算从WHERE a=b 转换为WHERE  TO_DATE(a)=b, 这也是为什么执行计划中出现INTERNAL_FUNCTION的原因-从实际的“二进制”执行计划生成可读性的执行计划的代码无法将内部操作码转换为相应的适合人们容易理解的函数名称,因此默认使用“INTERNAL_FUNCTION”字符串取而代之显示。 英文原文如下,可以对比理解(如果觉得翻译的不好的话)

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.

Un-unparseable Complex Expressions

执行计划中出现“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.

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

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_CURSOR从V$SQL_PLAN获取其执行计划的相关数据,谓词部分来自ACCESS_PREDICATES和FILTER_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_PREDICATES和FILTER_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 isn’t 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 cursor’s “binary” execution plan memory structure and translates it to human-readable execution plan output. There’s even a parameter controlling this V$SQL_PLAN behavior, if it’s 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_CURSOR和V$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

ORACLE数据库中执行计划出现INTERNAL_FUNCTION一定是隐式转换吗?的更多相关文章

  1. 无法执行 varchar 值到 varchar 的隐式转换,原因是,由于排序规则冲突,该值的排序规则未经解析。

    SELECT CONVERT(VARCHAR(100), 列名) FROM Table 提示错误: 无法执行 varchar 值到 varchar 的隐式转换,原因是,由于排序规则冲突,该值的排序规则 ...

  2. Oracle数据库查看执行计划

    基于ORACLE的应用系统很多性能问题,是由应用系统SQL性能低劣引起的,所以,SQL的性能优化很重要,分析与优化SQL的性能我们一般通过查看该SQL的执行计划,本文就如何看懂执行计划,以及如何通过分 ...

  3. ORACLE数据库查看执行计划的方法

    一.什么是执行计划(explain plan) 执行计划:一条查询语句在ORACLE中的执行过程或访问路径的描述. 二.如何查看执行计划 1: 在PL/SQL下按F5查看执行计划.第三方工具toad等 ...

  4. c++中istream类型到bool类型的隐式转换

    事情的起因是见到了这种用法: while(cin>>m>>n&&m&&n) { } 现在分析一下,cin>>m>>n返回 ...

  5. 基于Oracle的SQL优化(崔华著)-整理笔记-第2章“Oracle里的执行计划”

    详细介绍了Oracle数据里与执行计划有关的各个方面的内容,包括执行计划的含义,加何查看执行计划,如何得到目标SQL真实的执行计划,如何查看执行计划的执行顺序,Oracle数据库里各种常见的执行计划的 ...

  6. Oracle性能优化之Oracle里的执行计划

    一.执行计划 执行计划是目标SQL在oracle数据库中具体的执行步骤,oracle用来执行目标SQL语句的具体执行步骤的组合被称为执行计划. 二.如何查看oracle数据库的执行计划 oracle数 ...

  7. Oracle数据库中直方图对执行计划的影响

    在Oracle数据库中,CBO会默认目标列的数据在其最小值low_value和最大值high_value之间均匀分布,并按照均匀分布原则,来计算目标列 施加查询条件后的可选择率以及结果集的cardin ...

  8. 查看Oracle数据库中的执行计划

    1.set autotrace traceonly命令 2.explain plan for命令 1)explain plan for select * from dual; 2)select * f ...

  9. 如何在Oracle数据库中查看哪些用户在执行哪些SQL

    对于DBA来说,这是一个非常常见的问题,DBA需要找出以下问题: 1.哪些用户在跑哪些SQL? 2.一个特定的SQL是被哪个用户在执行? 3.一个特定的用户在跑哪些SQL? 从这些问题中可以很明显的看 ...

随机推荐

  1. linux下的du和df的区别

    du(disk usage)是通过搜索文件来计算每个文件的大小然后累加,du能看到的文件只是一些当前存在的,没有被删除的.他计算的大小就是当前他认为存在的所有文件大小的累加和. df(disk fre ...

  2. mysql 安装报错集合

    mysql-5.6.39 源码编译安装报错 报错信息: /tmp/ccV858jD.s: Assembler messages: /tmp/ccV858jD.s: Fatal error: can't ...

  3. Server MyEclipse Tomcat v7.0 was unable to start within 45 seconds. If the server requires more time

    启动Tomcat服务器时经常遇到这个错误, Server MyEclipse Tomcat v7.0 was unable to start within 45 seconds. If the ser ...

  4. 【SSL1455&1456】 电子老鼠闯迷宫 & 骑士游行

    考点概况: 广搜板子 题面: \[\Large\text{电子老鼠闯迷宫}\] \[Time~Limit:1000MS~~Memory~Limit:65536K\] Description 如下图12 ...

  5. 《手把手教你》系列进阶篇之1-python+ selenium自动化测试 - python基础扫盲(详细教程)

    1. 简介 如果你从一开始就跟着宏哥看博客文章到这里,基础篇和练习篇的文章.如果你认真看过,并且手动去敲过每一篇的脚本代码,那边恭喜你,至少说你算真正会利用Python+Selenium编写自动化脚本 ...

  6. 使用正则表达式实现(加减乘除)计算器(C#实现)

    起因:公司领导要求做一款基于行业规范的计算器, 然后需要用户输入一些数据,然后根据用户输入的数据满足某些条件后,再根据用户输入的条件二进行加减乘除运算.;-) 期间因为查找规范等形成数据表的某一列是带 ...

  7. Bomb后端云使用

    Bomb是后台云托管工具,在Bomb官网注册会员,即可免费使用后台数据库服务.https://www.bmob.cn/app/list 创建应用后,在设置里获取密钥.根据官方文档集成. 在集成后运行时 ...

  8. 大数据之Linux用户权限设置

    用户 是Linux系统工作中重要的一环, 用户管理包括 用户 与 组 管理,在Linux系统中, 不论是由本级或是远程登录系统, 每个系统都必须拥有一个账号, 并且对于不同的系统资源拥有不同的使用权限 ...

  9. CoderForces Round60-(1117A,1117B,1117C题解)

    A. Best Subsegment time limit per test 1 second memory limit per test 256 megabytes input standard i ...

  10. UVA-11995

    There is a bag-like data structure, supporting two operations:1 x Throw an element x into the bag.2 ...