关于字符和数字类型的索引,Oracle如何实现内部自动转换以及索引使用的验证测试
关于字符和数字类型的索引,Oracle如何实现内部自动转换以及索引使用的验证测试
发表人:tomszrp | 发表时间: 2006年二月10日, 18:51
起因:今天早上有同事问我如下语句为什么执行很慢:
select * from tbcs.acc_bank_debit_info a
where a.region=534 and
a.status=1 and
a.accountid=(select accountoid from tbcs.subscriber b
where b.status='US10' and
b.region=534 and
b.servnumber=13626373466);
备注:以上2表均是按照region进行分区的分区表,在accountid上都有索引.
根据以前的经验,我一看就知道是子查询未用上索引的缘故,因为servernumber是varchar类型的,而... where b.status='US10' and b.region=534 and b.servnumber=13626373466这种写法是用不上索引的。
修改为... where b.status='US10' and b.region=534 and b.servnumber='13626373466'后,性能马上得以解决。
为了验证我对这个观点的认识,就顺手做了个实验,结果和我期望的一致。
实验环境:
windows xp2 + Oracle9i + 760M
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE 9.2.0.1.0 Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production
验证过程如下:
SQL>create table zrp (
recoid number(14),
servnumber varchar2(9),
region number(3),
deal_date date ) tablespace study;
--编写脚本插入数据(多弄点感受一下效果)
SQL> begin
2 for i in 1..99999 loop
3 insert into zrp values(to_number('53109053'||to_char(i,'fm00000')),'8053'||to_char(i,'fm00000'),531,sysdate);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL 过程已成功完成。
SQL>
SQL> begin
2 for i in 1..99999 loop
3 insert into zrp values(to_number('53208053'||to_char(i,'fm00000')),'8053'||to_char(i,'fm00000'),532,sysdate);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL 过程已成功完成。
SQL>
...
SQL> select count(*) from zrp;
299997
SQL>
--分别在number类型和varchar2类型的字段上recoid和servnumber上建立索引
SQL> create index inx_zrp_oid on zrp(recoid);
索引已创建。
SQL> create index inx_zrp_serv on zrp (servnumber);
索引已创建。
SQL>
--下面开始测试验证过程,先验证number类型字段上的索引是否能对字符串进行自动转换,从而使用上索引.
select * from zrp where recoid=5310905310005
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.03 3 6 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.03 3 6 0 1
Misses in library cache during parse: 1
Optimizer mode: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID ZRP
1 INDEX RANGE SCAN INX_ZRP_OID (object id 30594)
********************************************************************************
select * from zrp where recoid='5310905310080'
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 2 6 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 2 6 0 1
Misses in library cache during parse: 1
Optimizer mode: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID ZRP
1 INDEX RANGE SCAN INX_ZRP_OID (object id 30594)
********************************************************************************
/*
结果证明:对于number类型的字段的索引,Oracle在内部能实现对字符串和number之间的自动转换,从而继续使用索引.下面在看看varchar2类型字段上的索引是否能对数字进行自动转换,从而使用上索引呢
*/
select * from zrp where servnumber='905310001'
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.02 2 6 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.03 2 6 0 1
Misses in library cache during parse: 1
Optimizer mode: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID ZRP
1 INDEX RANGE SCAN INX_ZRP_SERV (object id 30595)
********************************************************************************
select * from zrp where servnumber=905318899
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.35 0.45 799 1513 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.35 0.45 799 1513 0 1
Misses in library cache during parse: 1
Optimizer mode: CHOOSE
Parsing user id: 59
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS FULL ZRP
********************************************************************************
哈哈,看到了,太明显了。此时对于select * from zrp where servnumber=905318899 语句就用不上servnumber字段上的索引了.
分析总结:
当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换. 上面recoid是一个数值类型的索引列.
select * from zrp where recoid='5310905310080';
实际上,经过ORACLE类型转换, 语句转化为:
select * from zrp where recoid=to_number('5310905310080');
类型转换没有发生在索引列上,索引的用途没有被改变. 现在,而servnumber是一个字符类型的索引列.
select * from zrp where servnumber=905318899
这个语句被ORACLE转换为:
select * frorm zrp to_number(servnumber)=905318899;
因为内部发生的类型转换, 这个索引将不会被用到!
为什么前者不不是发生在列上而后者是呢,闲着没事将Oracle内部对varchar/char和number类型的数据结构做了一个dump,发现引起这个内部转换可能与数据的存储类型有关系。
因为dump的过程比较罗嗦,也不容易看懂,此处略去,引用别人的总结如下:
<<...
一、字符类型
观察dump出来的结果,可以发现以下几点:
1.对于每个字段,除了保存字段的值以外,还会保存当前字段中数据的长度。而且,
oracle没有把字段的长度定义或类型定义保存在block中,这些信息保存在oracle的数据字典里面。
2.根据dump的结果,可以看到,字符类型在数据库中是以ascii格式存储的。
3.char类型为定长格式,存储的时候会在字符串后面填补空格,而varchar2和long类型都是变长的.
二、数字类型
Oracle的NUMBER类型最多由三个部分构成,这三个部分分别是最高位表示位、数据部分、符号位。其中负数包含符号位,正数不会包括符号位。另外,数值0比较特殊,它只包含一个数值最高位表示位80,没有数据部分。
正数的最高位表示位大于80,负数的最高位表示位小于80。其中一个正数的最高位是个位的话,则最高位表示位为C1,百位、万位依次为C2、C3,百分位、万分为依次为C0、BF。一个负数的最高位为个位的话,最高位表示位为3E,百位、万位依次为3D、3C,百分位、万分位依次为3F、40。
数据部分每一位都表示2位数。这个两位数可能是从0到99,如果是数据本身是正数,则分别用二进制的1到64表示,如果数据本身是负数,则使用二进制65到2表示。
符号位用66表示。
对于上面提到的这些关系常数,Oracle之所以这样选择是有一定道理的,大家没事可以自己推导出来研究,我这里列出的意思是使大家先对NUMBER类型数据有一个大概的了解。
...>>
最后,我又翻阅了Oracle官方文档,找到了如下的说法,证实了上面的结论:
...
The following rules govern the direction in which Oracle makes implicit datatype conversions:
1) During INSERT and UPDATE operations, Oracle converts the value to the datatype of the affected column.
2) During SELECT FROM operations, Oracle converts the data from the column to the type of the target variable.
3) When comparing a character value with a NUMBER value, Oracle converts the character data to NUMBER.
4) When comparing a character value with a DATE value, Oracle converts thecharacter data to DATE.
5) When you use a SQL function or operator with an argument of a datatype other than the one it accepts,
Oracle converts the argument to the accepted datatype.
6) When making assignments, Oracle converts the value on the right side of the equal sign (=) to the datatype of the target of the assignment on the left side.
7) During concatenation operations, Oracle converts from noncharacter datatypes to CHAR or NCHAR.
8) During arithmetic operations on and comparisons between character and noncharacter datatypes,
Oracle converts from any character datatype to a number, date, or rowid, as appropriate.
In arithmetic operations between CHAR/VARCHAR2 and NCHAR/NVARCHAR2, Oracle converts to a number.
9) Comparisons between CHAR/VARCHAR2 and NCHAR/NVARCHAR2 types may entail different character sets.
The default direction of conversion in such cases is from the database character set to the national character set.
以上9个要点尤为重要,其他的可以不看,这9个声明一定要看.这一步骤,也再次证明了阅读Oracle官方文档的重要性.