复合索引是如何工作的?

http://tech.it168.com/o/2006-04-09/200604091434742.shtml

作者:ahlu

 
 
时间:05-01-30 17:38
 
复合索引是如何工作的?
比如表test有索引ind(a,b)在A,B两列建立索引,a列中等于100的有100,000行,而b等于200的有500行。
例如:查出a等于100,b等于200的所有语句
SELECT * FROM test
WHERE a='100' and b='200'
oracle是否会去搜索索引上a列满足条件的100,000行再搜索b的500行,还是直接搜索b列上的500行

作者:top100
 
 
 
 
时间:05-01-30 18:36
rbo模式中,使用a
对于复合索引,使用第一个字段

作者:biti_rainy
 
 
 
 
时间:05-01-30 18:50
你测试一下就应该清楚了

当然不会搜索 100,000行再搜索b的500行

更清楚的,可以dump 看一下索引的结构,a,b 是放在一起存储在索引中的,不存在先a后b的问题

作者:ahlu
 
 
 
 
时间:05-01-30 21:01
在rule的环境下吗?

作者:biti_rainy
 
 
 
 
时间:05-01-30 21:51
本问题和 rule or CBO 无关


oracle 的索引中存储复合索引,是把所有字段放在一起存储的,当复合索引所有字段都出现在where条件中的时候,是所有条件一起比较的,不存在先比较哪一个字段过滤然后再比较第二个字段……


  
  
SQL > create table test as select * from dba_objects; Table created. SQL > desc test Name Null ? Type -- --------------------------------------- -------- ---------------------------- OWNER VARCHAR2 ( 30 ) OBJECT_NAME VARCHAR2 ( 128 ) SUBOBJECT_NAME VARCHAR2 ( 30 ) OBJECT_ID NUMBER DATA_OBJECT_ID NUMBER OBJECT_TYPE VARCHAR2 ( 18 ) CREATED DATE LAST_DDL_TIME DATE TIMESTAMP VARCHAR2 ( 19 ) STATUS VARCHAR2 ( 7 ) TEMPORARY VARCHAR2 ( 1 ) GENERATED VARCHAR2 ( 1 ) SECONDARY VARCHAR2 ( 1 ) SQL > update test set owner = ' RAINY ' ; 6251 rows updated. SQL > commit ; Commit complete. SQL > create index test_ind on test(owner, object_id ); Index created. SQL > set autotrace traceonly SQL > select * from test where owner = ' RAINY ' and object_id = 2000 ; Execution Plan -- -------------------------------------------------------- 0 SELECT STATEMENT Optimizer = CHOOSE 1 0 TABLE ACCESS ( BY INDEX ROWID) OF ' TEST ' 2 1 INDEX (RANGE SCAN) OF ' TEST_IND ' (NON - UNIQUE ) Statistics -- -------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 1 physical reads 0 redo size 1155 bytes sent via SQL * Net to client 499 bytes received via SQL * Net from client 2 SQL * Net roundtrips to / from client 0 sorts (memory) 0 sorts ( disk ) 1 rows processed SQL > drop index test_ind; Index dropped. SQL > update test set owner = rownum; 6251 rows updated. Execution Plan -- -------------------------------------------------------- 0 UPDATE STATEMENT Optimizer = CHOOSE 1 0 UPDATE OF ' TEST ' 2 1 COUNT 3 2 TABLE ACCESS ( FULL ) OF ' TEST ' Statistics -- -------------------------------------------------------- 164 recursive calls 6403 db block gets 97 consistent gets 0 physical reads 1557952 redo size 620 bytes sent via SQL * Net to client 528 bytes received via SQL * Net from client 3 SQL * Net roundtrips to / from client 6 sorts (memory) 0 sorts ( disk ) 6251 rows processed SQL > commit ; Commit complete. SQL > create index test_ind on test(owner, object_id ); Index created. SQL > select * from test where owner = ' 1234 ' and object_id = 2000 ; no rows selected Execution Plan -- -------------------------------------------------------- 0 SELECT STATEMENT Optimizer = CHOOSE 1 0 TABLE ACCESS ( BY INDEX ROWID) OF ' TEST ' 2 1 INDEX (RANGE SCAN) OF ' TEST_IND ' (NON - UNIQUE ) Statistics -- -------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 1 physical reads 0 redo size 918 bytes sent via SQL * Net to client 368 bytes received via SQL * Net from client 1 SQL * Net roundtrips to / from client 0 sorts (memory) 0 sorts ( disk ) 0 rows processed


当然如果 复合索引的前导列重复的很多,则范围扫描(index range scan)的话 可能会出现问题。

作者:xzh2000
 
 
 
 
时间:05-01-30 22:14
那偶认为你的索引应该是(b,a)会更好一些。

作者:adamyang
 
 
 
 
时间:05-01-31 08:57
个人觉得对于index range scan来说
还是根据前导列找到满足条件的第一个叶子节点,由于所有叶子节点是一个链表,
扫描会是从找到的节点开始顺序向下遍历到满足前导列条件的最后一个节点,在这次遍历的同时,由于复合索引第二列和前导列存储在一起,所以也完成了对第二列值的过虑。

我觉得range的长度,还是受前导列影响的,
但过滤是在一次的 range scan完成的

个人一点理解,还请指导。

作者:biti_rainy
 
 
 
 
时间:05-01-31 09:29
如果a,b 两个条件都是等于,差异是存在的,但是差异是不大的,最多相差两三个 consistent gets

但是如果是范围扫描(比如 between / > < 一类)差异可能就大了。

作者:ahlu
 
 
 
 
时间:05-01-31 11:00
 
哪能告诉我范围扫描差异在哪里?
作者:biti_rainy
 
 
 
 
时间:05-01-31 11:22

Re:

 
哪能告诉我范围扫描差异在哪里?

我这里说的范围扫描 确切地说应该说 范围查询

因为范围查询是从一个入口点进去找到 叶子,然后从叶子之间的指针逐个找下去,如果碰巧该范围中有一段特别多的数据那将 搜索大量的数据

而如果是符合索引的两个字段条件都是等于并且满足条件的结果集比较小,那它是直接从 索引的枝上定位到相关叶子,则问题不是很大。


这是因为 a,b 两个字段的索引的key是存储在一起的,不存在先过滤一遍a再过滤一遍b的问题。也就是说,如果是符合索引,我们假定存在数字 a = 1000, b为 1..1000000 的记录。总记录条数为100万条,b是唯一的。若a做前导列的索引(a,b)。 实际上,如果查询总是同时存在 a,b 的等于条件,则效果就相当于 1000*100万 + 1..100万 这样的100万个值的单个字段做索引。

我再重复一遍我上面做过的实验,希望你仔细看一看!


  
  
SQL > desc t Name Null ? Type -- --------------------------------------- -------- ---------------------------- OWNER VARCHAR2 ( 30 ) OBJECT_NAME VARCHAR2 ( 128 ) SUBOBJECT_NAME VARCHAR2 ( 30 ) OBJECT_ID NUMBER DATA_OBJECT_ID NUMBER OBJECT_TYPE VARCHAR2 ( 18 ) CREATED DATE LAST_DDL_TIME DATE TIMESTAMP VARCHAR2 ( 19 ) STATUS VARCHAR2 ( 7 ) TEMPORARY VARCHAR2 ( 1 ) GENERATED VARCHAR2 ( 1 ) SECONDARY VARCHAR2 ( 1 ) SQL > update t set owner = ' sys ' ; 481216 rows updated. SQL > commit ; Commit complete. SQL > update t set OBJECT_ID = rownum; 481216 rows updated. SQL > commit ; Commit complete. SQL > create index t_ind12 on t(owner, object_id ); Index created. SQL > set autotrace traceonly SQL > select * from t where owner = ' sys ' and object_id = 12345 ; Execution Plan -- -------------------------------------------------------- 0 SELECT STATEMENT Optimizer = CHOOSE 1 0 TABLE ACCESS ( BY INDEX ROWID) OF ' T ' 2 1 INDEX (RANGE SCAN) OF ' T_IND12 ' (NON - UNIQUE ) Statistics -- -------------------------------------------------------- 0 recursive calls 0 db block gets 5 consistent gets 2 physical reads 0 redo size 1156 bytes sent via SQL * Net to client 499 bytes received via SQL * Net from client 2 SQL * Net roundtrips to / from client 0 sorts (memory) 0 sorts ( disk ) 1 rows processed SQL > create index t_ind21 on t( object_id ,owner); Index created. SQL > select * from t where owner = ' sys ' and object_id = 12345 ; Execution Plan -- -------------------------------------------------------- 0 SELECT STATEMENT Optimizer = CHOOSE 1 0 TABLE ACCESS ( BY INDEX ROWID) OF ' T ' 2 1 INDEX (RANGE SCAN) OF ' T_IND21 ' (NON - UNIQUE ) Statistics -- -------------------------------------------------------- 0 recursive calls 0 db block gets 5 consistent gets 2 physical reads 0 redo size 1156 bytes sent via SQL * Net to client 499 bytes received via SQL * Net from client 2 SQL * Net roundtrips to / from client 0 sorts (memory) 0 sorts ( disk ) 1 rows processed SQL >
在下面这个例子中 对于前导列a字段来说,恰好 'sys' 满足 > 'a' 这个条件,但是一开始你可能并不清楚会有这么多的相同的满足 >'a' 的值,这不过是举个例子,比如a换做数字类型,恰好大量数字a = 100,你查询一个 a between 99 and 100,你可能并不会认为大量记录在里面,但是实际上却还不如全表扫描。

SQL> select * from t where owner > 'a';

481216 rows selected.


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T'
2 1 INDEX (RANGE SCAN) OF 'T_IND12' (NON-UNIQUE)




Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
70913 consistent gets
1245 physical reads
0 redo size
29217583 bytes sent via SQL*Net to client
353390 bytes received via SQL*Net from client
32083 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
481216 rows processed

作者:ahlu
 
 
 
 
时间:05-02-05 01:31
其实我觉得查找大结果集,索引反而会拖累速度
就想你看书一样,当你要看大多数页时,还不如直接去翻书,看目录反而慢。
在问个问题,每次取索引快时,使取出满足条件的一个块后直接去访问表,还是等取出所有满足条件的索引块后再去访问表?有什么办法使表中数据能顺序排列,这样当取出一个索引块后也能对应一个表块,如果无序状态下,可能一个索引块中的rowid会对应多个表块,并且其他索引块也会访问相同的表块,这可能造成极大的浪费,可能也是大量逻辑读产生的原因!

作者:adamyang
 
 
 
 
时间:05-02-05 09:23
Re: 其实我觉得查找大结果集,索引反而会拖累速度
quote:
最初由 ahlu 发布
就想你看书一样,当你要看大多数页时,还不如直接去翻书,看目录反而慢。
在问个问题,每次取索引快时,使取出满足条件的一个块后直接去访问表,还是等取出所有满足条件的索引块后再去访问表?有什么办法使表中数据能顺序排列,这样当取出一个索引块后也能对应一个表块,如果无序状态下,可能一个索引块中的rowid会对应多个表块,并且其他索引块也会访问相同的表块,这可能造成极大的浪费,可能也是大量逻辑读产生的原因!


尽管觉得用书的目录做比喻有些欠妥
(用书的索引更恰当些吧)
但道理是一样的,在选择性底的列上建立索引意义不大,
在对表中很多数据检索时,索引还不如全表。
在良好表分析的前提下CBO可以决定一部分这种选择了。
 

作者:biti_rainy
 
 
 
 
时间:05-02-05 09:26
Re: 谢谢!其实我觉得查找大结果集,索引反而会拖累速度
quote:
最初由 ahlu 发布
就想你看书一样,当你要看大多数页时,还不如直接去翻书,看目录反而慢。
在问个问题,每次取索引快时,使取出满足条件的一个块后直接去访问表,还是等取出所有满足条件的索引块后再去访问表?有什么办法使表中数据能顺序排列,这样当取出一个索引块后也能对应一个表块,如果无序状态下,可能一个索引块中的rowid会对应多个表块,并且其他索引块也会访问相同的表块,这可能造成极大的浪费,可能也是大量逻辑读产生的原因!


你这个话 是正确的,但是和你 前面的问题是两码事


索引本就不适合查询大量数据,这是基本原则问题

至于表数据和索引顺序保持一致,这个问题讨论过多次了,可以参考
http://blog.itpub.net/post/330/2970

如果非要保持数据顺序的问题,可以参考 IOT 表(为什么不广泛使用,肯定是有原因的,自己多去琢磨吧)

作者:ahlu
 
 
 
 
时间:05-02-05 10:57
我知道iot的问题,其实和索引一样,需要维护
但他重建起来很费事,我觉得iot很适合dss

作者:ahlu
 
 
 
 
时间:05-02-05 11:10
呵呵,看了biti的blog,和我想的差不多
在问个问题,解析sql-〉建立和执行执行计划-〉读取结果集-〉显示结果集,哪个最费时间,他们各占时间的百分比是多少?

作者:biti_rainy
 
 
 
 
时间:05-02-05 11:32
一般都是消耗在查询读取这个环节,如果返回记录特多,达到数百万以上,可能在网络传输一层也要消耗一些时间,不过由于这段时间和 读取记录过程是重叠的,所以不是简单的叠加。

BTW : 具体情况下你完全可以测试,怎么能用 百分比 来一概而论呢?
select 1 from dual

select * from big_table 难道能用个百分比来做统一结论?

作者:ahlu
 
 
 
 
时间:05-02-05 11:46
我觉得在复合索引中oracle会选择,where子句中范围最大那列开始查找
而无所谓是不是前导列,但我又在想在rule下,oracle没有统计资料,他怎么会知道哪个列范围大?

作者:biti_rainy
 
 
 
 
时间:05-02-05 12:01
Re: 我觉得在复合索引中oracle会选择,where子句中范围最大那列开始查找
quote:
最初由 ahlu 发布
而无所谓是不是前导列,但我又在想在rule下,oracle没有统计资料,他怎么会知道哪个列范围大?

where 条件中哪个列 范围大,也就是说选择性的差异吧


前导列 是针对具体某个索引而言, where 条件中各列到底是针对多个索引而言? 如果不是,对于一个索引的话,哪里存在 哪个列的选择性的问题?对于一个复合索引来说,只要符合索引的列都存在于where条件中,那就不存在什么哪个列先过滤再比对另一个列的问题,我做了2次实验不都说明了这个问题么?


rule下,oracle根本不会考虑选择性问题!有着既定的不变的规则,你有疑问,请做实验证明之,实验有问题不可理解再探讨行不行呢,就这样本就没有理清头绪的讨论,不过是兜圈子而已。
 

作者:ahlu
 
 
 
 
时间:05-02-05 12:14
我喜欢把问题形象化,看了biti的解释想了想?
sql的运行过程就象你到图书馆去借书,当你和图书管理员说你要鲁迅的全部书籍(SELECT * FROM PUB WHERE NAME='鲁迅'),图书管理员会很快想出执行计划也就是访问路径,但找到书会很多,时间是浪费在搬运书的过程中,如果你和图书管理员说你要1980年清华出版社出版的以sk开头的书籍,这时有多种选择路径,图书管理员可能要盘算一下了,最后选择查索引,只找出了几本书,这时时间是花费在选择路径上,还有个问题,如果你要以s开头的书,可能只有几本,但也可能会很多,这时一个刚上任的图书管理员回去机械的查索引,运气好只有几本书,会很快但运气不好很多书还不如直接到书架上去查(这就是基于rule的缺点),一个工作多年的管理员,会知道大概查询书有多少,如果他估计会很多,他会直接去查书架(这就是基于cost,收集表的统计信息),问题是这个工作多年的管理员因某些原因很久没有上班了,图书馆又进了很多书,或又转移了很多书,下次查询时他很能会选择错误的路径,这就是基于cost的不稳定性和需要定时维护的缺点,管理员需要不断的去了解书的库存状况,当图书馆很大时会很费时会影响工作

作者:biti_rainy
 
 
 
 
时间:05-02-05 12:35
但是我还是看不出你上面这个描述,和你前面的问题之间有什么矛盾

你本来不过是一个定性话的描述,又如何想要定量呢? 管理国家图书馆的人和管理自己家藏书的人,你能统一量化各个环节的代价的百分比吗?

作者:ahlu
 
 
 
 
时间:05-02-05 13:04
我只是想分析一下
现在我的工作受到限制,像捆住手脚的dba,不能动数据库设置,只能用rule,所以sql提示,和索引的建立是我最关心的,只是想看看还有什么方法在受限的条件下能优化sql,比如如果oracle在分析和建立执行计划会很费时,会在某些sql语句会很费时,那么我是否可以让oracle按照我的执行计划来做,这样时间就省下来了,就想你直接告诉管理员怎么找,省得他想了,所以想分析一下,那种情况下oracle可能会在分析和建立执行计划是会花很多时间。

作者:biti_rainy
 
 
 
 
时间:05-02-05 13:31
一般来说分析和建立执行计划是不需要太多的时间的,只是搜集统计数据的时候数据量大的话可能很消耗时间。

通常来说,如果调整 optimizer_max_permutations 这个参数可能多表连接的执行计划的产生消耗的时间变长,至于这个参数,你自己可以去搜索


如果你要稳定执行计划,能修改sql的话,完全可以自己使用hints就好了


多去读一读 sql tuning 和 cbo 相关的文章,eygle 的不少文章阐述了很多相关问题


BTW: 至于帖子中的问题,因为你思路一直在变化,后面说的和前面的根本已经没有关系了,在对整体不了解的情况下,最好先先系统地学习相关知识,然后就某些知识点逐个提问。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值