访问路径的选择--返回少量数据时一定走索引吗?

本文探讨了数据库优化器在返回少量数据时是否总会选择索引的问题。通过实验展示了即使返回10%的数据,表T1由于数据有序,优化器选择索引访问;而表T2因数据分散,选择了全表扫描。执行计划差异源于索引的聚簇因子,揭示了数据物理分布对查询效率的影响。
-- 问题:
当某字段上有索引的表返回少量数据(如,10%数据)时,优化器一定会走索引吗?
-- 实验:
-- 1.建立两张表
-- a.T1表
--drop table t1 purge ;
create table t1 nologging as
select trunc((rownum-1)/100) id
       ,rpad(rownum,100)     t_pad
  from dba_source
 where rownum <= 10000 ;
 
create index t1_idx1 on t1(id) ;


select owner,segment_name
       ,bytes/1024/1024    
  from dba_segments
 where segment_name = 't1'
       ;
       
exec dbms_stats.gather_table_stats( 'SYS','t1',method_opt => 'FOR ALL COLUMNS SIZE 1',cascade    => TRUE) ;
 
-- b.T2表 
--drop table t2 purge ;
create table t2 nologging as
select mod(rownum,100)       id
       ,rpad(rownum,100)     t_pad
  from dba_source
 where rownum <= 10000 ;
 
create index t2_idx1 on t2(id) ;


exec dbms_stats.gather_table_stats( 'SYS','t2',method_opt => 'FOR ALL COLUMNS SIZE 1',cascade    => TRUE) ;


-- 2.查看各自的执行计划
set autotrace traceonly
-- a.T1表
select * from t1 where id = 1 ;
执行计划
----------------------------------------------------------
Plan hash value: 2623418078


---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |   100 | 10400 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1      |   100 | 10400 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T1_IDX1 |   100 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------


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


   2 - access("ID"=1)




统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
      12650  bytes sent via SQL*Net to client
        586  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        100  rows processed
          
-- b.T2表          
select * from t2 where id = 1 ;
执行计划
----------------------------------------------------------
Plan hash value: 1513984157


--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   100 | 10400 |    43   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T2   |   100 | 10400 |    43   (0)| 00:00:01 |
--------------------------------------------------------------------------


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


   1 - filter("ID"=1)




统计信息
----------------------------------------------------------
         24  recursive calls
          0  db block gets
        166  consistent gets
          0  physical reads
          0  redo size
      12330  bytes sent via SQL*Net to client
        586  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        100  rows processed


从上可知,T1表走了索引,但是T2表却走全表扫描。两张表返回的结果是一样的,但是为什么导致执行计划的不同呢?
这里面涉及到一个叫聚簇因子的度量值。
索引的聚簇因子向优化器表明具有相同键值的数据块在物理存储中的分布情况。用一个接近于表中数据块数量的较低数值
来表明该表中基于索引后的数据行的排序度或聚簇度是很高的。而用接近于表中的数据行数来表明索引条目的聚簇度很低。
          
通过查询上面两个表的聚簇因子,可以知道为什么T1走索引,而T2走全表。这是因为T1的数据分布比较有序,所以通过索引访问
只需少量的IO。而T2表的数据比较分散,而且索引扫描是单块读(IFFS除外)的,而全表扫描是多块读的,
所以优化器认为T2表走全表扫描比索引扫描更高效。
因而可以得出一个结论,一个表是否走索引不仅与返回的数据量有关,还与数据的物理分布有关。
-- 查询表的聚簇因子          
col idx_name          format a16
col clustering_factor format 99999
col blocks            format 99999
col num_rows          format 99999
select a.table_name||'.'||a.index_name          idx_name
       ,a.clustering_factor
       ,b.blocks
       ,b.num_rows
  from dba_indexes a
       ,dba_tables b
 where 1 = 1
   and a.table_name = b.table_name 
   and b.table_name in ('t1','t2')
 order by a.table_name,a.index_name ;   
IDX_NAME         CLUSTERING_FACTOR BLOCKS NUM_ROWS
---------------- ----------------- ------ --------
t1.T1_IDX1                  152    152    10000
t2.T2_IDX1                10000    152    10000                
<think>我们讨论的是Oracle数据库,用户想知道为字段code和group创建索引后,执行`SELECT group, code`是否会使用索引。 注意:在Oracle中,GROUP是保留字(用于GROUP BY子句),因此如果表中有名为"group"的列,在查询通常需要用双引号括起来(创建如果用了双引号,则使用也必须用双引号且大小写一致)。但为了表述方便,我们假设这里的列名是合法的(比如实际列名可能是"group_name"等,但用户问题中写的是"group"和"code"),我们按原问题中的列名讨论。 首先,在Oracle中,索引的使用取决于多个因素,包括: 1. 查询条件(WHERE子句) 2. 索引的类型(单列索引、复合索引等) 3. 统计信息 4. 查询优化器的决策 用户的问题是:执行`SELECT group, code`是否会使用索引?这里没有提到WHERE条件,也没有提到索引是如何创建的。 假设我们创建了一个复合索引: ```sql CREATE INDEX idx_group_code ON table_name ("group", code); ``` 那么,当我们执行以下查询: ```sql SELECT "group", code FROM table_name; ``` 这个查询没有WHERE条件,是一个全表扫描。但是,如果这个复合索引包含了所有要查询的列(即"group"和code),那么Oracle可能会选择使用索引而不是表,因为索引扫描可能比全表扫描更快(特别是当索引的大小比表小很多,比如表中有很多其他大字段)。 这种情况称为“索引覆盖扫描”(Index-Only Scan),在Oracle中,如果索引包含了查询所需的所有列,那么优化器可能会选择只扫描索引而不访问表。 但是,如果索引只包含部分列,或者没有包含所有列,那么优化器可能仍然选择全表扫描。 另外,即使索引包含了所有列,优化器也可能根据统计信息(如表的数据量、索引的聚类因子等)决定使用全表扫描还是索引扫描。 因此,具体是否使用索引,需要看: 1. 索引是否包含查询中涉及的所有列(即覆盖索引) 2. 优化器根据统计信息判断哪个访问路径更高效 我们可以通过执行计划来确认。 示例: 创建表: ```sql CREATE TABLE test_table ( id NUMBER PRIMARY KEY, "group" VARCHAR2(10), code VARCHAR2(10), other_column VARCHAR2(100) ); ``` 创建复合索引: ```sql CREATE INDEX idx_group_code ON test_table ("group", code); ``` 然后,我们查询: ```sql EXPLAIN PLAN FOR SELECT "group", code FROM test_table; ``` 查看执行计划: ```sql SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); ``` 如果执行计划中显示的是“INDEX FAST FULL SCAN”或“INDEX FULL SCAN”,则表示使用了索引。 注意: - 如果表的数据量很小,优化器可能仍然选择全表扫描,因为全表扫描的成本可能更低。 - 如果索引的聚类因子很高(即索引的顺序和表的物理存储顺序相似),那么索引扫描可能更高效。 因此,对于`SELECT group, code`这样的查询,如果存在一个包含这两列的复合索引,那么Oracle很可能会使用该索引进行索引覆盖扫描,从而避免访问数据。 但是,如果查询中包含了索引中没有的列,比如: ```sql SELECT "group", code, other_column FROM test_table; ``` 那么,即使有索引(`"group", code`),由于索引中不包含`other_column`,所以Oracle需要回表查询,这优化器可能会选择全表扫描(特别是当需要返回大部分行),或者先索引扫描再回表(如果WHERE条件能利用索引筛选少量行)。 所以,结论是: 如果索引是覆盖索引(即索引包含查询的所有列),那么Oracle可能会使用索引进行扫描(INDEX FAST FULL SCAN等),但具体是否使用取决于优化器的成本估算。 如果没有覆盖索引,那么即使有索引,也不会用于这种没有WHERE条件的查询(因为没有条件,索引无法用于过滤,而如果索引不包含所有列,那么回表操作的成本可能比全表扫描更高)。 因此,针对用户的问题,我们需要知道: 1. 索引是否包含group和code两列(即是否为覆盖索引)? 2. 表的数据量大小? 3. 是否有其他条件? 但根据问题描述,我们假设索引是覆盖索引,那么执行`SELECT group, code`是有可能使用索引的。 最后,注意:在Oracle中,如果列名是保留字,必须用双引号括起来,且区分大小写。所以实际中要确保列名正确引用。 引用[1]中提到了索引的使用情况,可以通过查询计划来查看是否使用了索引,以及索引的类型。 我们还可以通过hint来强制使用索引,如引用[2]所示,但一般不建议,因为优化器通常能做出更好的选择。 所以,回答用户:如果创建的索引包含了group和code两列(即复合索引),那么在执行`SELECT group, code`Oracle可能会使用该索引(通过索引覆盖扫描)来避免访问表,从而提高查询效率。但是否实际使用,需要查看执行计划。 如果索引是单独的两个单列索引(一个在group上,一个在code上),那么这种查询不太可能同使用两个索引(因为没有WHERE条件),优化器可能会选择全表扫描。 因此,建议创建复合索引(group, code)来实现覆盖索引扫描。 相关问题: 1. 如何查看Oracle执行计划以确定索引使用情况? 2. 什么是覆盖索引?它在哪些场景下特别有效? 3. 在Oracle中,如何创建复合索引? 4. 当查询中包含索引未覆盖的列,如何优化查询? 5. Oracle优化器在什么情况下会选择全表扫描而不是索引扫描? 根据用户的问题,我们生成了以上相关问题,供用户进一步了解。</think>在 Oracle 数据库中,执行 `SELECT group, code` 是否会使用索引取决于以下关键因素: ### 1. **索引类型和覆盖情况** - **单列索引**:若仅为 `group` 或 `code` 单独创建索引(如 `CREATE INDEX idx_group ON table_name(group)`),查询 `SELECT group, code` **不会使用索引**,因为: - 无 `WHERE` 条件过滤数据 -返回多列数据,单列索引无法覆盖查询 - **复合索引**:若创建覆盖 `(group, code)` 的复合索引: ```sql CREATE INDEX idx_group_code ON table_name(group, code); ``` - 当执行 `SELECT group, code` ,**可能触发索引覆盖扫描(INDEX FAST FULL SCAN)**,因为索引已包含所有查询列,无需回表[^1][^2]。 - **优化器决策依据**:表数据量、索引聚类因子、统计信息等(通过执行计划确认)。 --- ### 2. **执行计划验证方法** 通过 `EXPLAIN PLAN` 检查是否使用索引: ```sql EXPLAIN PLAN FOR SELECT group, code FROM table_name; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); ``` - **关键输出解读**: - `INDEX FAST FULL SCAN`:使用复合索引覆盖扫描 ✅ - `TABLE ACCESS FULL`:全表扫描(未用索引) ❌ - `possible_keys`:显示可用索引 - `key`:实际使用的索引(若为 `idx_group_code` 则生效)[^1] --- ### 3. **强制使用索引(HINT)** 若优化器未选择索引,可用 `/*+ INDEX(...) */` 提示强制使用: ```sql SELECT /*+ INDEX(t idx_group_code) */ group, code FROM table_name t; ``` > **注意**:需谨慎使用 HINT,可能干扰优化器决策[^2]。 --- ### 4. **索引使用场景总结** | 查询类型 | 是否使用索引 | 条件说明 | |--------------------------|--------------|------------------------------| | `SELECT group, code` | ✅ 可能 | 需复合索引 `(group, code)` | | `SELECT *` | ❌ 不会 | 需回表查询所有列 | | `SELECT group WHERE code=1` | ✅ 可能 | 需索引 `(code, group)` | --- ### 5. **优化建议** 1. **创建复合索引**: ```sql CREATE INDEX idx_group_code ON table_name(group, code); ``` 2. **更新统计信息**(确保优化器准确决策): ```sql EXEC DBMS_STATS.GATHER_TABLE_STATS('SCHEMA_NAME', 'TABLE_NAME'); ``` 3. **避免全表扫描**:对大表优先验证执行计划。 --- ### 示例验证 ```sql -- 创建复合索引 CREATE INDEX idx_group_code ON employees(department_id, employee_id); -- 检查执行计划 EXPLAIN PLAN FOR SELECT department_id, employee_id FROM employees; -- 输出结果(应出现 INDEX FAST FULL SCAN) SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); ``` > **结论**:当为 `(group, code)` 创建复合索引,`SELECT group, code` 大概率使用索引覆盖扫描,但需通过执行计划最终确认[^1][^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值