由于SYS_OP_C2C内部转换,Oracle SQL执行计划发生了变化

时间:2014-10-10 09:31:45

标签: sql oracle oracle11g

我想知道为什么这个查询的费用

select * from address a
left join name n on n.adress_id=a.id
where a.street='01';

高于

select * from address a
left join name n on n.adress_id=a.id
where a.street=N'01';

地址表看起来像这样

ID              NUMBER
STREET          VARCHAR2(255 CHAR)
POSTAL_CODE     VARCHAR2(255 CHAR)

和名称表看起来像这样

ID              NUMBER
ADDRESS_ID      NUMBER
NAME            VARCHAR2(255 CHAR)
SURNAME         VARCHAR2(255 CHAR)

这些是解释计划

返回的费用

解释'01'的计划

-----------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                      |  3591 |  1595K|    87   (0)| 00:00:02 |
|   1 |  NESTED LOOPS OUTER          |                      |  3591 |  1595K|    87   (0)| 00:00:02 |
|*  2 |   TABLE ACCESS FULL          | ADDRESS              |     3 |   207 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID| NAME                 |  1157 |   436K|    47   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | NAME_HSI             |  1157 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("A"."STREET"='01')
   4 - access("N"."ADDRESS_ID"(+)="A"."ID")

解释N'01'的计划

-----------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                      |   347 |   154K|    50   (0)| 00:00:01 |
|   1 |  NESTED LOOPS OUTER          |                      |   347 |   154K|    50   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL          | ADDRESS              |     1 |    69 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID| NAME                 |  1157 |   436K|    47   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | NAME_HSI             |  1157 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(SYS_OP_C2C("A"."STREET")=U'01')
   4 - access("N"."ADDRESS_ID"(+)="A"."ID")

正如您所见,N'01'查询的成本低于'01'的成本。知道为什么吗? N'01'还需要将varchar转换为nvarchar,因此成本应该更高(SYS_OP_C2C())。另一个问题是为什么N'01'查询处理的行低于'01'?

[编辑]

  • address有30行。
  • name有19669行。

3 个答案:

答案 0 :(得分:7)

SYS_OP_C2C是一个internal function,使用implicit conversion函数执行varchar2 national character setTO_NCHAR SQL> CREATE TABLE t AS SELECT 'a'||LEVEL col FROM dual CONNECT BY LEVEL < 1000; Table created. SQL> SQL> EXPLAIN PLAN FOR SELECT * FROM t WHERE col = 'a10'; Explained. SQL> SELECT * FROM TABLE(dbms_xplan.display); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------- Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 5 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| T | 1 | 5 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------- 1 - filter("COL"='a10') 13 rows selected. SQL> 。因此,与使用正常比较的滤波器相比,滤波器完全改变。

我不确定行数 less 的原因,但我可以保证它也可以更多。成本估算不会受到影响。

让我们尝试逐步查看测试用例。

SQL> EXPLAIN PLAN FOR SELECT * FROM t WHERE col = N'a10';

Explained.

SQL> SELECT * FROM TABLE(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |    10 |    50 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T    |    10 |    50 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------

   1 - filter(SYS_OP_C2C("COL")=U'a10')

13 rows selected.

SQL>

到目前为止一切顺利。由于只有一行的值为&#39; a10&#39;,优化程序估计一行。

让我们看看国家角色转换。

filter(SYS_OP_C2C("COL")=U'a10')

这里发生了什么?我们可以看到varchar2,这意味着应用了内部函数,并将nvarchar2值转换为function-based index。过滤器现在找到了10行。

这也将抑制任何索引使用,因为现在在列上应用了一个函数。我们可以通过创建full table scan来调整它以避免SQL> create index nchar_indx on t(to_nchar(col)); Index created. SQL> SQL> EXPLAIN PLAN FOR SELECT * FROM t WHERE to_nchar(col) = N'a10'; Explained. SQL> SELECT * FROM TABLE(dbms_xplan.display); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------- Plan hash value: 1400144832 -------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10 | 50 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 10 | 50 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | NCHAR_INDX | 4 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------- --------------------------------------------------- 2 - access(SYS_OP_C2C("COL")=U'a10') 14 rows selected. SQL>

{{1}}

但是,这会使执行计划类似吗?不,我认为使用两个不同的字符集,过滤器将不会被应用。因此,不同之处在于。

我的研究表明,

  

通常,当数据来自应用程序时会出现这种情况   是nvarchar2类型,但表列是varchar2。因此,Oracle   在过滤操作中应用内部函数。我的建议   是,以便更好地了解您的数据,以便您在使用期间使用类似的数据类型   设计阶段。

答案 1 :(得分:3)

当担心解释计划时,关于表格的当前统计数据是否重要。如果统计数据不能很好地代表实际数据,那么优化器就会出错并错误估计基数。

您可以通过查询数据字典来查看收集统计信息的时间:

select table_name, last_analyzed
  from user_tables
 where table_name in ('ADDRESS','NAME');

您可以通过调用DBMS_STATS来收集要使用的优化程序的统计信息:

begin
   dbms_stats.gather_table_stats(user, 'ADDRESS');
   dbms_stats.gather_table_stats(user, 'NAME');
end;

因此,也许在收集统计数据后,您将获得不同的解释计划。也许不是。

解释计划的不同主要是因为优化器估计在两种情况下它在地址表中会找到多少行。

在第一种情况下,你有一个具有相同数据类型的等式谓词 - 这是好的,优化器通常可以很好地估计基数(行数)这样的情况。

在第二种情况下,一个函数应用于列 - 这通常很糟糕(除非你有基于函数的索引)并且会强制优化器进行猜测。随着优化器的开发人员试图对其进行改进,不同版本的Oracle将会有所不同。一些版本的疯狂猜测将简单地类似于“我猜表中行数的5%。”

在比较不同的数据类型时,最好避免隐式转换,特别是在这种情况下,隐式转换在而不是文字上创建一个函数。如果您遇到的值为NVARCHAR2数据类型且需要在上面的谓词中使用它,那么将值显式转换为列的数据类型可能是个好主意。

select * from address a
left join name n on n.adress_id=a.id
where a.street = CAST( N'01' AS VARCHAR2(255));

在这种情况下使用文字,当然没有意义。在这里,您只需使用第一个查询。但如果它是一个变量或函数参数,也许您可​​以使用用例来做这样的事情。

答案 2 :(得分:0)

正如我所看到的,第一个查询返回3591行,第二个返回347行。因此,Oracle需要较少的I / O操作,这就是为什么成本较低的原因。

不要与

混淆
  

N'#39; 01&#39;还需要将varchar转换为nvarchar

Oracle执行一次硬解析,然后对相同的查询使用软解析。所以oracle工作的时间越长,它就越快。

相关问题