我想知道为什么这个查询的成本
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行。
SYS_OP_C2C
是一个 internal function
哪个 implicit conversion
的 varchar2
至 national character set
运用 TO_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>
到现在为止还挺好。由于只有一行的值为'a10',优化器估计一行。
让我们看看国家字符转换。
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>
这里发生了什么?我们可以看到 filter(SYS_OP_C2C("COL")=U'a10')
,这意味着应用了一个内部函数,它转换了 varchar2
价值 nvarchar2
。过滤器现在找到10行。
这也将抑制任何索引使用,因为现在在列上应用了一个函数。我们可以通过创建一个来调整它 function-based index
避免 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>
但是,这会使执行计划类似吗?不,我想 两个不同的角色 ,过滤器不会被应用。因此,不同之处在于。
我的研究说,
通常,当通过应用程序发送数据时会出现这种情况
是nvarchar2类型,但表列是varchar2。因此,Oracle
在过滤器操作中应用内部函数。我的建议
是,以便更好地了解您的数据,以便您在使用期间使用类似的数据类型
设计阶段。
在担心解释计划时,重要的是是否有关于表格的最新统计数据。如果统计数据不能很好地表示实际数据,那么优化器将会出错并错误地估计基数。
您可以通过查询数据字典来检查收集统计信息的时间:
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));
在这种情况下,使用文字它当然没有意义。在这里,您只需使用第一个查询。但如果它是一个变量或函数参数,也许你可以使用用例来做这样的事情。
正如我所看到的,第一个查询返回3591行,第二个返回347行。因此Oracle需要较少的I / O操作,这就是成本较低的原因。
不要混淆
N'01'还需要将varchar转换为nvarchar
Oracle执行一次硬解析,然后对相同的查询使用软解析。所以oracle工作的时间越长,它就越快。