ORACLE数据库中执行计划出现INTERNAL_FUNCTION一定是隐式转换吗?
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一定是隐式转换吗?的更多相关文章
- 无法执行 varchar 值到 varchar 的隐式转换,原因是,由于排序规则冲突,该值的排序规则未经解析。
SELECT CONVERT(VARCHAR(100), 列名) FROM Table 提示错误: 无法执行 varchar 值到 varchar 的隐式转换,原因是,由于排序规则冲突,该值的排序规则 ...
- Oracle数据库查看执行计划
基于ORACLE的应用系统很多性能问题,是由应用系统SQL性能低劣引起的,所以,SQL的性能优化很重要,分析与优化SQL的性能我们一般通过查看该SQL的执行计划,本文就如何看懂执行计划,以及如何通过分 ...
- ORACLE数据库查看执行计划的方法
一.什么是执行计划(explain plan) 执行计划:一条查询语句在ORACLE中的执行过程或访问路径的描述. 二.如何查看执行计划 1: 在PL/SQL下按F5查看执行计划.第三方工具toad等 ...
- c++中istream类型到bool类型的隐式转换
事情的起因是见到了这种用法: while(cin>>m>>n&&m&&n) { } 现在分析一下,cin>>m>>n返回 ...
- 基于Oracle的SQL优化(崔华著)-整理笔记-第2章“Oracle里的执行计划”
详细介绍了Oracle数据里与执行计划有关的各个方面的内容,包括执行计划的含义,加何查看执行计划,如何得到目标SQL真实的执行计划,如何查看执行计划的执行顺序,Oracle数据库里各种常见的执行计划的 ...
- Oracle性能优化之Oracle里的执行计划
一.执行计划 执行计划是目标SQL在oracle数据库中具体的执行步骤,oracle用来执行目标SQL语句的具体执行步骤的组合被称为执行计划. 二.如何查看oracle数据库的执行计划 oracle数 ...
- Oracle数据库中直方图对执行计划的影响
在Oracle数据库中,CBO会默认目标列的数据在其最小值low_value和最大值high_value之间均匀分布,并按照均匀分布原则,来计算目标列 施加查询条件后的可选择率以及结果集的cardin ...
- 查看Oracle数据库中的执行计划
1.set autotrace traceonly命令 2.explain plan for命令 1)explain plan for select * from dual; 2)select * f ...
- 如何在Oracle数据库中查看哪些用户在执行哪些SQL
对于DBA来说,这是一个非常常见的问题,DBA需要找出以下问题: 1.哪些用户在跑哪些SQL? 2.一个特定的SQL是被哪个用户在执行? 3.一个特定的用户在跑哪些SQL? 从这些问题中可以很明显的看 ...
随机推荐
- python中的random模块简析
在Python生成随机数用random模块,下面的文章是本人自己简单总结的ython生成随机数与random模块中最常用的几个函数的关系,希望对大家有所帮助. random.random()用于生成随 ...
- DG中模拟failover故障与恢复
问题描述:情形是当主库真正出现异常之后,才会执行的操作,那么我们执行过failover 之后,如何在重新构建DG,这里我们利用flashback database来重构.模拟前主库要开启闪回区,否则要 ...
- 【Java基础】字面量相加的类型转换
Java字面量的相加类型转换 1.Java 编译期间(javac),凡是字面量和常量的运算,都会先运算出结果 2.运行期当字符串池中有 String"字面量"时,Java 会直接用 ...
- sql server建库建表(数据库和数据表的常用操作)
数据库和数据表 (开发常用 操作) 一,数据库的创建 一个SQLServer 是由两个文件组成的:数据文件(mdf) 和日志文件(ldf),所以我们创建数据库就是要为其指定数据库名.数据文件和日志文件 ...
- Djangoday1 入门及第一个apphelloworld
1 Django基础指令新建一个django project新建app创建数据库表,更新数据库表或字段使用开发服务器清空数据库创建超级管理员导出数据 导入数据Django 项目环境终端数据库命令行更多 ...
- python中的局部变量和全局变量
- 使用Feign访问接口
添加主要依赖 使用Feign访问接口的配置,如果服务不在Eureka上,可以不加Eureka的依赖,用在FeignClient上指定url的方式访问 dependencies { compile(' ...
- 【Java】Java中的final关键字和static
0.概述 final关键字表示是不可变的: 下面分别从属性(字段).方法.类中进行说明: 1.属性(or字段),表示常量 final声明在属性(or字段)中,表示常量,有两种初始化方法,1是在声明时直 ...
- python BeautifulSoup4 获取 script 节点问题
在爬取12306站点名时发现,BeautifulSoup检索不到station_version的节点 因为script标签在</html>之外,如果用‘lxml’解析器会忽略这一部分,而使 ...
- python数据挖掘第二篇-爬虫
python爬虫 urllib用法 eg1: from urllib import request data = request.urlopen(urlString).read() # data获取的 ...