梁敬彬梁敬弘兄弟出品
往期回顾
左右SQL执行计划妙招 ①——子查询的应用范围
左右SQL执行计划妙招 ②——Hint无效原因
左右SQL执行计划妙招 ③——执行计划SQL写法差异改变【with子句】
左右SQL执行计划妙招 ④——执行计划SQL写法差异改变【insert all】
左右SQL执行计划妙招 ⑤——执行计划利用设计特性改变【并行度影响】
左右SQL执行计划妙招 ⑥——执行计划SQL写法差异改变【rownum分页】
左右SQL执行计划妙招 ⑦——执行计划SQL写法差异改变【rownum实体化】
在SQL性能优化过程中,SQL的写法细节常常对执行计划产生不可忽视的影响。合理利用Oracle数据库的特性,能够让同一业务实现方案下的SQL拥有更高的执行效率。本篇将聚焦于rowid的使用,通过两个实际对比例子,详细剖析不同SQL写法对执行计划的影响,为SQL优化实战提供借鉴。
环境准备
首先,构建测试环境并初始化数据:
drop table t purge;
create table t as select * from dba_objects;
update t set object_id = rownum;
commit;
create index idx_object_id on t(object_id);
set autotrace off;
select rowid from t where object_id=8;
此时,可以查到object_id=8的数据行对应的rowid,例如:
ROWID
-----
AAAaq2AALAAApOrAAH
SQL写法与执行计划对比
1. 普通条件更新(语句1)
update t set object_name='abc' where object_id=8;
执行计划:
-----------------------------------------------------------------------------------
Plan hash value: 3890322047
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 1 | 79 | 2 (0)| 00:00:01 |
| 1 | UPDATE | T | | | | |
|* 2 | INDEX RANGE SCAN| IDX_OBJECT_ID | 1 | 79 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
- dynamic sampling used for this statement (level=2)
统计信息:
30 recursive calls
2 db block gets
75 consistent gets
0 physical reads
448 redo size
680 bytes sent via SQL*Net to client
616 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
该语句的执行计划中,Oracle选择通过idx_object_id索引进行INDEX RANGE SCAN,然后定位数据行并执行更新。
2. 使用rowid精确定位(语句2)
update t set object_name='abc' where object_id=8 and t.rowid='AAAaq2AALAAApOrAAH';
执行计划:
------------------------------------------------------------------------------------
Plan hash value: 815416494
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 1 | 91 | 1 (0)| 00:00:01 |
| 1 | UPDATE | T | | | | |
|* 2 | TABLE ACCESS BY USER ROWID| T | 1 | 91 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------------------------------------------------
2 - filter("OBJECT_ID"=8)
统计信息:
0 recursive calls
3 db block gets
1 consistent gets
0 physical reads
0 redo size
682 bytes sent via SQL*Net to client
650 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
在此语句中,加入了rowid作为查询条件。Oracle直接根据rowid访问对应数据行,执行计划变为TABLE ACCESS BY USER ROWID,这一方式被认为是直接、跳跃式访问表中某条记录的最快途径。
分析
从执行计划及统计信息对比可以看出:
语句1 先通过索引查找,再定位到表中的记录,适合object_id存在索引的场景。
语句2 由于rowid已明确,无须再借助索引,可以绕过索引查找的步骤,直接到达物理存储位置。Oracle明确选择TABLE ACCESS BY USER ROWID这一最快的单行定位方式,提升了访问效率。
实际应用中,在已知rowid的情况下,采用rowid作为定位依据,可以减少数据库的消耗,实现最短路径访问。在批量或高并发更新场景下,尤其能展现出rowid方式的优势。
使用注意事项
- ROWID的时效性
- DDL操作影响:表重建、分区操作会改变ROWID
- 数据移动影响:行迁移、行链接可能导致ROWID变化
- 时间窗口:ROWID应在短时间内使用,避免失效
- 业务逻辑考虑
- 数据一致性:确保ROWID对应的行仍然满足业务条件
- 并发控制:考虑其他会话可能的数据修改
- 错误处理:处理ROWID无效的情况
- 性能权衡
- 查询开销:获取ROWID本身也需要成本
- 应用复杂度:增加了应用逻辑的复杂性
- 适用范围:并非所有场景都适合使用ROWID
小结
在特定的业务场景下,语句1和语句2在逻辑上完全等价,但性能表现却截然不同。TABLE ACCESS BY USER ROWID是Oracle中最快的访问方式,它通过直接的物理定位避免了索引查找的开销,大幅减少了逻辑读和递归调用。
理解和掌握ROWID的使用技巧,能够在适当场景下显著提升SQL执行效率。然而,ROWID的使用需要谨慎考虑其时效性和业务适用性,在性能优化和代码复杂度之间找到合适的平衡点。
未完待续…
左右SQL执行计划妙招 ⑨——执行计划SQL写法差异改变【缓存结果影响】