ORACLE 用ROWID 删除数据竟然的走索引-不得其解

本文通过实例分析了SQL执行计划未利用索引的原因,并详细介绍了如何通过分区表优化来提升查询效率,包括索引分析、分区操作及参数调整等关键步骤。

下午一个同事执行一条删除语句1个小时才执行了11万条

该语句是DELETE FROM TABLE_NAME WHERE ROWID=’XXXXAAAA’

执行计划确实走索引一个以CREATE_TIME为主的索引.

后来很多同事不得其解本以为写了ROWID,那该直接去表里把数据给删了,为啥还要去访问索引呢? 我们的DBA解释到因为该表没做分析索引也没做分析.

 

不过回家实验了一把瞧瞧看

SQL> create table t_test (id number,name varchar2(35),create_time date);

Table created

SQL> create index idx_test_createtime on t_test(create_time);

Index created

SQL> insert into t_test  select object_id,object_name,sysdate from dba_objects;

50333 rows inserted

SQL> commit;

Commit complete

不做分析:

      

一些默认的参数   

SQL> show parameter optimizer_index_cost_adj

optimizer_index_cost_adj             integer     100

optimizer_index_caching              integer     0

db_block_buffers                     integer     0

db_block_checking                   string      FALSE

db_block_checksum                  string      TRUE

db_block_size                       integer     8192

db_file_multiblock_read_count         integer     32

 

执行删除语句看执行计划

delete t_test where id >500

delete t_test where id=:1

delete t_test where rowed=’ AAAM3OAAEAAAAOEAAA’

结果执行计划没走索引

DELETE STATEMENT, GOAL = ALL_ROWS   Depth=0 Operation=DELETE STATEMENT      DELETE  Depth=1    Operation=DELETE    Object name=T_TEST                        TABLE ACCESS BY USER ROWID Depth=2    Operation=TABLE ACCESS     

分析下

SQL> analyze table t_test compute statistics;

Table analyzed

发现还是不走索引

 

试下分区表

create table t_test_2 as select * from t_test

SQL> update t_test_2 set create_time=sysdate-1 where id<=10000;

9581 rows updated

SQL> update t_test_2 set create_time=sysdate-2 where id<=20000 and id>10000;

9974 rows updated

SQL> update t_test_2 set create_time=sysdate-3 where id<=30000 and id>20000;

10000 rows updated

SQL> update t_test_2 set create_time=sysdate-4 where id<=40000 and id>30000;

9997 rows updated

SQL> update t_test_2 set create_time=sysdate-5 where id<=50000 and id>40000;

9089 rows updated

SQL> commit;

Commit complete

SQL> select to_char(create_time,'yyyymmdd') as statedate,count(*) t from t_test_2 group by to_char(create_time,'yyyymmdd');

STATEDATE          T

--------- ----------

20120528        9974

20120527       10000

20120530        1692

20120529        9581

20120526        9997

20120525        9089

6 rows selected

 

创建时间范围分区表:

-- Create table

create table t_test_3

(  id          number,

  name        varchar2(35),

  create_time date

)partition by range (create_time)

(  partition test_20120525 values less than (to_date('2012-05-26','yyyy-mm-dd')),

  partition test_20120526 values less than (to_date('2012-05-27','yyyy-mm-dd')),

  partition test_20120527 values less than (to_date('2012-05-28','yyyy-mm-dd')),

  partition test_20120528 values less than (to_date('2012-05-29','yyyy-mm-dd')),

  partition test_20120529 values less than (to_date('2012-05-30','yyyy-mm-dd')),

  partition test_20120530 values less than (to_date('2012-05-31','yyyy-mm-dd'))

);

创建本地索引并插入数据

SQL> create index idx_local_createtime on t_test_3 (create_time) local;

Index created

SQL> insert into t_test_3  select id,name,create_time from t_test_2;

50333 rows inserted

SQL> commit;

Commit complete

 

delete t_test where id=:1

delete t_test where rowed=’ AAAM3OAAEAAAAOEAAA’

执行计划依旧是TABLE ACCESS BY USER ROWID

 

分别设置参数

SQL> alter system set optimizer_index_caching=100;

System altered

SQL> alter system set optimizer_index_cost_adj=1;

System altered

结果执行计划依旧是TABLE ACCESS BY USER ROWID

 

执行分析

SQL> EXEC dbms_stats.gather_table_stats

  (ownname=>'SHARK', tabname=>'T_TEST_3',estimate_percent=>100,cascade=>true);

PL/SQL procedure successfully completed

执行计划依旧是TABLE ACCESS BY USER ROWID

 

创建子分区

SQL> update t_test_2 set id=mod(id,10);

50333 rows updated

SQL>CREATE TABLE SHARK.t_test_4
(
 
id           NUMBER,
  name         VARCHAR2(35),
 
create_time  DATE
)
PARTITION BY RANGE (create_time)
SUBPARTITION BY LIST (id)
SUBPARTITION TEMPLATE
  (SUBPARTITION "A" VALUES (0),
  
SUBPARTITION "B" VALUES (1),
  
SUBPARTITION "C" VALUES (2),
  
SUBPARTITION "D" VALUES (3),
  
SUBPARTITION "E" VALUES (4),
  
SUBPARTITION "F" VALUES (5),
  
SUBPARTITION "G" VALUES (6),
  
SUBPARTITION "H" VALUES (7),
  
SUBPARTITION "J" VALUES (8),
  
SUBPARTITION "K" VALUES (9)
 
)

  PARTITION TEST_20120525 VALUES LESS THAN (to_date('2012-05-26', 'yyyy-mm-dd')), 
  PARTITION TEST_20120526 VALUES LESS THAN  (to_date('2012-05-27', 'yyyy-mm-dd')) , 
  PARTITION TEST_20120527 VALUES LESS THAN (to_date('2012-05-28', 'yyyy-mm-dd'))   
  PARTITION TEST_20120528 VALUES LESS THAN  (to_date('2012-05-29', 'yyyy-mm-dd'))   
  PARTITION TEST_20120529 VALUES LESS THAN (to_date('2012-05-30', 'yyyy-mm-dd'))   
  PARTITION TEST_20120530 VALUES LESS THAN  (to_date('2012-05-31', 'yyyy-mm-dd'))   
);

 

SQL> insert into t_test_4 select * from t_test_2;

50333 rows inserted

SQL> commit;

Commit complete

SQL> create index idx_local_createtime_test4 on t_test_4 (create_time) local;

Index created

 

SQL> EXEC dbms_stats.gather_table_stats 

(ownname=>'SHARK', tabname=>'T_TEST_4',estimate_percent=>100,cascade=>true);

PL/SQL procedure successfully completed

SQL> create index idx_local_id_test4 on t_test_4 (id) local;

Index created

 

很不幸运的是执行计划依旧是TABLE ACCESS BY USER ROWID

只有下面的语句才用到了索引

delete t_test_4 where id =4

delete

  partition range all

   partition list single

     index range scan

 

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production

TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production

 公司的数据库服务器应该是10.2.0.4的吧? 涉及到表都是分区,或者子分区.本地索引,未分析!

<think>首先,用户的问题是:“我想理解Oracle数据库中使用索引查询时为何需要回表,以及是否因为查询字段包含id导致请问Oracle使用索引查询时为什么会回表?在查询语句select索引,idfromtablewhere索引=1中是否因包含id字段而导致回表?”用户的核心问题是:1.为什么在使用索引查询时需要回表?2.在查询语句`select索引,idfromtablewhere索引=1`中,是否因为包含`id`字段而导致回表?我需要参考提供的引用内容来回答。引用包括:-[^1]:讨论OracleSQL没有过滤条件是否会索引,提到Oracle不会为所有列的nullable属性都为Y的SQL语句索引-[^2]:解释MySQL的访问类型,如const、ref等,但这是MySQL的,用户问的是Oracle,所以需要谨慎使用。-[^3]:关于Oracle查询索引对应列,提到表名在系统表中是大写的。-[^4]:直接相关:讨论Oracle组合索引与回表。指出如果联合索引的多个字段都在where谓词中,可以减少回表次数,甚至避免回表。-[^5]:关于Oracle添加索引的脚本。关键引用是[^4]:"如果联合索引中的多个字段都在where谓词中出现了,则联合索引效率比单列索引高,因为通过多个条件可以从索引中过滤得到更少的记录条数,也就减少了需要回表扫描的次数,甚至可以直接在联合索引中得到所查的所有结果,则不再需要回表。"这解释了回表的概念:回表是指当索引不包含所有查询所需的列时,数据库需要回原表获取完整数据。现在,回答用户问题:1.**为什么需要回表?**-索引通常只存储索引键和行标识符(如ROWID)。如果查询需要额外的列,而这些列不在索引中,数据库必须使用行标识符从原表中获取这些数据。这就是回表。-Oracle中,索引条目包含索引键和ROWIDROWID指向数据行的物理位置。2.**在查询`select索引,idfromtablewhere索引=1`中,是否因包含`id`字段而导致回表?**-假设"索引"是指索引列名。例如,假设有一个索引在列`索引列`上。-查询是:`select索引列,idfromtablewhere索引列=1`。-如果索引是单列索引(只包含`索引列`),那么索引条目包含`索引列`的值和ROWID-查询需要返回`索引列`和`id`。索引中已经有`索引列`的值,但没有`id`的值。因此,数据库需要回表获取`id`列。-只有当索引包含所有查询所需的列时,才能避免回表。这称为索引覆盖查询(coveringindex)。-引用[^4]提到:如果联合索引包含多个字段,且所有查询字段都在索引中,则可以避免回表。在用户查询中:-`where索引=1`:索引列用于过滤,所以索引会被使用。-`select索引,id`:需要返回索引列和id列。-如果索引是单列索引(只包含索引列),那么id列不在索引中,因此需要回表获取id。-如果索引是组合索引(包含索引列和id),那么如果索引包含这两列,就可以直接从索引获取数据,避免回表。用户说"索引",可能是指索引列名。在查询中,`索引`可能是一个列名,比如有一个列叫"索引",但这样命名不常见。更合理的解释是,用户意指"索引列",即被索引的列。在查询`select索引,idfromtablewhere索引=1`中,我假设"索引"是列名,被索引。所以,回答:-回表的原因:索引不包含所有查询列。-在这个查询中,由于select列表包含id列,如果id不在索引中,就需要回表。从引用[^4],可以强调组合索引的好处。回答结构:1.解释回表概念。2.分析用户的具体查询。3.给出避免回表的建议(如使用组合索引)。4.添加相关问题,作为系统指令要求。系统指令要求:-所有行内数学表达式用`$...$`格式。但这个问题是数据库相关的,可能没有数学表达式,除非涉及性能公式。-独立公式用`$$...$$`,但可能不需要。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾添加引用标识。-回答结构清晰,逐步解决问题。生成相关问题:指令说"生成相关问题",并在示例中有"§§相关问题§§"。在回答末尾添加相关问题。引用标识:在引用内容末尾加[^n]。参考引用:-[^4]直接讨论回表。-其他引用可能相关,但[^4]是最直接的。最终回答大纲:1.**解释回表**:-索引存储键值和ROWID-当查询需要额外列时,数据库使用ROWID回表获取数据-引用[^4]:"减少了需要回表扫描的次数"。2.**分析用户查询**:-查询:`select索引列,idfromtablewhere索引列=1`。-如果索引是单列索引索引列),则索引包含索引列值和ROWID-查询需要id列,所以需要回表获取id。-因此,包含id字段会导致回表,除非id在索引中。3.**避免回表的建议**:-使用组合索引包含所有查询列。-例如,创建索引包含(索引列,id),这样查询可以直接从索引获取数据,避免回表。-引用[^4]支持这个观点。4.**相关问题**:-生成3-5个相关问题,如:-如何创建索引覆盖查询以避免回表?-回表操作对性能的影响是什么?-Oracle中如何查看索引是否被使用?-等。确保回答真实可靠。</think>### Oracle数据库中索引查询与回表机制解析 在Oracle数据库中,索引查询时可能发生“回表”(Table Access by Index ROWID),这是数据库优化器处理查询的一种常见行为。下面我将逐步解释回表的原因,并结合您的具体查询(`select 索引, id from table where 索引 = 1`)分析是否因包含 `id` 字段而导致回表。回答基于Oracle索引机制和引用内容,确保真实可靠。 --- ### 1. **为什么索引查询时需要回表?** 回表是指数据库通过索引定位到行后,仍需回原表(堆表)获取完整数据行的过程。原因如下: - **索引的存储结构**:Oracle索引(如B树索引)存储索引键值(例如 `索引列` 的值)和对应的ROWID(行物理地址)。ROWID用于定位数据行在表段中的位置。 - **查询需求超出索引范围**:如果查询需要返回的列(如 `id`)未包含在索引中,优化器必须使用ROWID回原表读取这些额外列的数据。这增加了I/O开销,可能影响性能。 - **避免回表的条件**:只有当索引包含所有查询所需的列时(称为“覆盖索引”),优化器可直接从索引返回数据,无需回表。引用[^4]指出:“如果联合索引中的多个字段都在where谓词中出现了,则联合索引效率比单列索引高,因为通过多个条件可以从索引中过滤得到更少的记录条数,也就减少了需要回表扫描的次数,甚至可以直接在联合索引中得到所查的所有结果,则不再需要回表。”[^4] 简而言之,回表的发生是由于索引未“覆盖”所有查询列,导致数据库需补充访问原表。 --- ### 2. **分析您的查询:是否因包含 `id` 字段导致回表?** 针对查询语句: `select 索引, id from table where 索引 = 1` 假设“索引”是列名(如列名为 `索引列`),且该列已创建单列索引(非组合索引)。以下是详细分析: - **场景假设**: - 索引类型:单列索引(仅包含 `索引列`)。 - 查询需求:返回 `索引列` 和 `id` 两列。 - WHERE条件:`索引列 = 1`(使用索引过滤)。 - **是否发生回表?** - **是,回表会发生**。原因: - 单列索引只存储 `索引列` 的值和ROWID,不包含 `id` 的值。 - 查询需返回 `id` 列,因此优化器在通过索引找到匹配行后,必须使用ROWID回原表读取 `id` 的数据- 这符合回表机制:索引无法提供所有SELECT列,需补充访问表数据- **如何验证?** - 使用 `EXPLAIN PLAN` 查看执行计划。如果出现 `TABLE ACCESS BY INDEX ROWID`,则表明发生回表。 - 示例执行计划可能显示: ``` Operation | Name | Rows -------------------------|------------|------ INDEX RANGE SCAN | IDX_COL | 1 -- 使用索引过滤 TABLE ACCESS BY INDEX ROWID | TABLE_NAME | 1 -- 回表获取id列 ``` 这证明回表是由查询中包含 `id` 字段(未在索引中)引起的。 - **避免回表的方法**: - **创建组合索引**:将 `索引列` 和 `id` 包含在同一个索引中。例如: ```sql CREATE INDEX idx_covering ON table(索引列, id); ``` 这样索引直接覆盖查询所需的所有列,避免回表(引用[^4]强调组合索引可减少回表)[^4]。 - **优化查询**:如果 `id` 是主键或唯一列,确保索引包含它;或仅查询索引列(如 `select 索引 from table where 索引 = 1`),但这不适用于您的需求。 - **约束设计**:如引用[^1]建议,通过添加非空约束或主键优化索引行为(例如 `ALTER TABLE table MODIFY id NOT NULL`)[^1]。 --- ### 3. **性能影响与最佳实践** - **回表的性能代价**:回表增加磁盘I/O和CPU开销,尤其在大型表中。性能取决于回表行数(WHERE过滤后的结果集大小)。 - **优化建议**: - 优先使用组合索引覆盖常用查询列。 - 监控索引使用:通过 `ALL_IND_COLUMNS` 查看索引包含的列(引用[^3]提醒需用 `UPPER` 处理表名大小写)[^3]。 - 避免全表扫描:确保WHERE条件能有效使用索引(如引用[^2]讨论的访问类型 `const` 或 `ref`)[^2]。 - **实际案例**:在您的查询中,添加 `id` 字段导致回表,但通过组合索引可优化。批量添加索引脚本(如引用[^5])可自动化此过程[^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值