Oracle OR条件的优化与改写
一、 同一字段谓词条件的or
1. 构造测试表
create table t_do as select * from dba_objects;
create index t_do_n1 on t_do(object_name);
create index t_do_n2 on t_do(owner);create table t_dt as select * from dba_tables;
create index t_dt_n1 on t_dt(table_name);create table t_di as select * from dba_indexes;
create index t_di_n1 on t_di(index_name);
2. 原SQL
SELECT object_type,object_name
FROM t_do
WHERE owner = 'SYS'
AND
( object_name = 'T3'
OR object_name IN (SELECT table_name FROM t_dt WHERE table_name LIKE 'TRIGER%')
OR object_name IN (SELECT index_name FROM t_di WHERE index_name LIKE 'IND_OBJ%')
)
order by 1;
由于or的写法,t3表虽然object_name字段有索引,选择率也高,但用不了。
3. 改写方法
对于同一个字段多个or条件的,可以直接改为 字段 in (select xxx union all select xxx)
SELECT object_type,object_name
FROM t_do
WHERE owner = 'SYS'
and object_name in
(
select 'T3' from dual
union all
SELECT table_name FROM t_dt WHERE table_name LIKE 'TRIGER%'
union all
SELECT index_name FROM t_di WHERE index_name LIKE 'IND_OBJ%'
)
order by 1;
4. 补充
12.2版本中对 or 进行了加强,可以尝试添加 /*+ or_expand */ hint来优化SQL。
SELECT /*+ or_expand */ object_type,object_name
FROM t_do
WHERE owner = 'SYS'
AND
( object_name = 'T3'
OR object_name IN (SELECT table_name FROM t_dt WHERE table_name LIKE 'TRIGER%')
OR object_name IN (SELECT index_name FROM t_di WHERE index_name LIKE 'IND_OBJ%')
)
order by 1;
从执行计划可以看到,它是把三部分分别做关联后再union all,明显复杂很多,效果没有改写的好。
二、 两个字段谓词条件的or
select object_name,object_type,object_id from t1 where object_name='T1' or object_id<=10;
优化方法:如果字段选择率高,两个字段分别加索引即可
三、 高选择度的or改写
上面这种情况,如果两个字段的选择性可以,而且都存在索引,不论是oracle还是mysql,优化器都是会自动改写的。如果要手工改写,可以这样改:
select object_name,object_type,object_id from t1 where object_name='T1'
union all
select object_name,object_type,object_id from t1 where object_id<=10 and lnnvl(object_name='T1') ;
等价改写是用union all,而不是union,既不等价,效率又低
这里跟 第一部分“同一字段谓词条件的or” 的区别是:or的两边是两个不同的字段,两个条件数据可能会有交集,因此不能只用 union all,还需要加 lnnvl(object_name='T1') 去除查询中的重复值。注意也不要写成object_name<>'T1',两种不等价。
lnnvl用于某个语句的where子句中的条件,如果条件为true就返回false;如果条件为UNKNOWN或者false就返回true。非oracle库没有lnnvl函数,其实 lnnvl(object_name='T1') 就相当于(object_name<>'T1' or object_name is null)。
四、 or与半连接 in/exists
select object_name,object_type,object_id from t1 where object_name='T1' or exists (select 1 from t2 where t1.object_id=t2.object_id and t2.object_id<=10);
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 112 (100)| | 9 |00:00:00.01 | 424 |
|* 1 | FILTER | | 1 | | | | | 9 |00:00:00.01 | 424 |
| 2 | TABLE ACCESS FULL| T1 | 1 | 23754 | 2134K| 112 (1)| 00:00:01 | 22943 |00:00:00.01 | 410 |
|* 3 | FILTER | | 22943 | | | | | 9 |00:00:00.01 | 14 |
|* 4 | INDEX RANGE SCAN| IND_T2_N2 | 9 | 1 | 13 | 1 (0)| 00:00:01 | 9 |00:00:00.01 | 14 |
--------------------------------------------------------------------------------------------------------------------------
这种情况优化器就不会自动帮你改写了,需要手动改写(12.2版本及以上可以自动使用or_expand做查询转换)。
本质上改写的思想还是:小结果用nest loop,大结果集用hash join。
1. 小结果集的改写
object_name='T1' 结果集小,还是按照上面的思路:
select object_name,object_type,object_id from t1 where object_name='T1'
union all
select object_name,object_type,object_id from t1 where exists (select 1 from t2 where t1.object_id=t2.object_id and t2.object_id<=10) and lnnvl(object_name='T1');
-----------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 6 (100)| | 9 |00:00:00.01 | 15 | 1 |
| 1 | UNION-ALL | | 1 | | | | 9 |00:00:00.01 | 15 | 1 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 1 | 92 | 1 (0)| 00:00:01 | 0 |00:00:00.01 | 2 | 0 |
|* 3 | INDEX RANGE SCAN | IND_T1_N1 | 1 | 1 | | 1 (0)| 00:00:01 | 0 |00:00:00.01 | 2 | 0 |
| 4 | NESTED LOOPS | | 1 | 1 | 105 | 5 (20)| 00:00:01 | 9 |00:00:00.01 | 13 | 1 |
| 5 | NESTED LOOPS | | 1 | 1 | 105 | 5 (20)| 00:00:01 | 9 |00:00:00.01 | 11 | 1 |
| 6 | VIEW | VW_SQ_1 | 1 | 9 | 117 | 2 (0)| 00:00:01 | 9 |00:00:00.01 | 2 | 0 |
| 7 | HASH UNIQUE | | 1 | 1 | 117 | | 9 |00:00:00.01 | 2 | 0 |
|* 8 | INDEX RANGE SCAN | IND_T2_N2 | 1 | 9 | 117 | 2 (0)| 00:00:01 | 9 |00:00:00.01 | 2 | 0 |
|* 9 | INDEX RANGE SCAN | IND_T1_N2 | 9 | 1 | | 1 (0)| 00:00:01 | 9 |00:00:00.01 | 9 | 1 |
|* 10 | TABLE ACCESS BY INDEX ROWID | T1 | 9 | 1 | 92 | 2 (0)| 00:00:01 | 9 |00:00:00.01 | 2 | 0 |
-----------------------------------------------------------------------------------------------------------------------------------------------------
2. 大结果集的改写
object_name='T1' 结果集大
原SQL
select count(owner) from t1 where object_name LIKE 'W%' or exists (select 1 from t2 where t1.object_id=t2.object_id and owner='SYS');
COUNT(OWNER)
------------
14866
---------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 112 (100)| | 1 |00:00:00.06 | 43900 |
| 1 | SORT AGGREGATE | | 1 | 1 | 145 | | | 1 |00:00:00.06 | 43900 |
|* 2 | FILTER | | 1 | | | | | 14866 |00:00:00.06 | 43900 |
| 3 | TABLE ACCESS FULL | T1 | 1 | 23754 | 3363K| 112 (1)| 00:00:01 | 22943 |00:00:00.01 | 409 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T2 | 21528 | 132 | 10428 | 2 (0)| 00:00:01 | 13451 |00:00:00.04 | 43491 |
|* 5 | INDEX RANGE SCAN | IND_T2_N2 | 21528 | 80 | | 1 (0)| 00:00:01 | 21527 |00:00:00.02 | 21964 |
---------------------------------------------------------------------------------------------------------------------------------------------
改写思路
- 大结果集用hash join --> 改写为表关联
- inner join 还是 left join? --> left join,因为有or条件,必须保证 object_name LIKE 'W%' 部分的条件不被关联条件过滤掉(这跟单纯的半连接改写是不一样的)。因此t1表必须是主表,关联后返回其所有数据。
- 如何实现exists条件 --> 关联后t2.object_id is not null,就是关联后有匹配上的( t1和t2表中均存在的)
- 半关联自带去重 --> t2表的子查询要加 distinct
改写后语句
select count(owner)
from t1
left join (select distinct object_id from t2 where owner='SYS') v_t2
on t1.object_id=v_t2.object_id
where ( t1.object_name='EMP' or v_t2.object_id is not null);
SQL> select count(owner)2 from t13 left join (select distinct object_id from t2 where owner='SYS') v_t24 on t1.object_id=v_t2.object_id5 where ( t1.object_name like 'W%' or v_t2.object_id is not null);COUNT(OWNER)
------------14866----------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | | 474 (100)| | 1 |00:00:00.01 | 840 |
| 1 | SORT AGGREGATE | | 1 | 1 | 158 | | | | 1 |00:00:00.01 | 840 |
|* 2 | FILTER | | 1 | | | | | | 14866 |00:00:00.01 | 840 |
|* 3 | HASH JOIN RIGHT OUTER| | 1 | 23754 | 3665K| | 474 (1)| 00:00:01 | 22943 |00:00:00.01 | 840 |
| 4 | VIEW | | 1 | 13210 | 167K| | 362 (1)| 00:00:01 | 14556 |00:00:00.01 | 431 |
| 5 | HASH UNIQUE | | 1 | 13210 | 1019K| 1160K| 362 (1)| 00:00:01 | 14556 |00:00:00.01 | 431 |
|* 6 | TABLE ACCESS FULL | T2 | 1 | 13210 | 1019K| | 118 (1)| 00:00:01 | 14556 |00:00:00.01 | 431 |
| 7 | TABLE ACCESS FULL | T1 | 1 | 23754 | 3363K| | 112 (1)| 00:00:01 | 22943 |00:00:00.01 | 409 |
----------------------------------------------------------------------------------------------------------------------------------
3. 实例验证
下面我们通过一个简化的例子,看看大结果改写中间每一步的结果输出,加深理解。
- 构造测试表
SQL> create table ta(id int,name varchar2(10),owner varchar2(10));Table created.SQL> create table tb(id int,name varchar2(10),owner varchar2(10));Table created.SQL> select * from ta;ID NAME OWNER
---------- ---------- ----------1 A SYS2 B TMP3 W SYSSQL> select * from tb;ID NAME OWNER
---------- ---------- ----------1 A SYS2 B TMP3 W TMP4 C TMP5 D SYS
- 原SQL
select * from ta where name LIKE 'W%'
or exists (select 1 from tb where ta.id=tb.id and owner='SYS'); ID NAME OWNER
---------- ---------- ----------1 A SYS3 W SYS
- name LIKE 'W%':ta表id=3这行一定要保留
- tb.owner='SYS':tb表保留id=1和5
- ta.id=tb.id:只有 id=1 符合
- 最后ta表返回id=1和3的数据
- 改写为 inner join 会怎么样?
下面就是当做普通的半连接来改写:
SQL> select * from ta join (select distinct id from tb where owner='SYS') v_tb on ta.id=v_tb.id where (ta.name like 'W%' or v_tb.id is not null);
ID NAME OWNER ID
---------- ---------- ---------- ----------
1 A SYS 1
- 子查询:b表返回 id=1,5
- inner join:on优先级比where高,按照ta.id=v_tb.id ,a表一下只剩id=1(其实就错在这步,ta有or条件,应该作为主表,不论能不能匹配到tb,都应该返回所有数据)
- where 条件:匹配到 v_tb.id is not null 该行返回;没有 ta.name like 'W%' 的行了,忽略
- 改写为 left join 会怎么样?
首先我们不加 v_tb.id is not null,可以看到 根据left join含义 ta表的所有行都被保留了下来。但是 id=2 这一行,既不符合tb表中存在,又不符合 ta.name like 'W%',所以应该是要过滤掉的。
SQL> select * from ta left join (select distinct id from tb where owner='SYS') v_tb on ta.id=v_tb.id;
ID NAME OWNER ID
---------- ---------- ---------- ----------
1 A SYS 1
2 B TMP
3 W SYS
增加条件 v_tb.id is not null 仅保留tb匹配行,最终返回结果与原sql一致
SQL> select * from ta left join (select distinct id from tb where owner='SYS') v_tb on ta.id=v_tb.id where (ta.name like 'W%' or v_tb.id is not null);
ID NAME OWNER ID
---------- ---------- ---------- ----------
1 A SYS 1
3 W SYS
五、 update语句中的or
原SQL是这样的,expiretime字段有索引,但OR的写法导致无法用到,绝大部分数据只能在回表时做filter,效率很低。
1. 测试案例
我们做一个简化版的例子:
create table t3(id int,name varchar2(20),state int);insert into t3 values(1,'aaa',0);
insert into t3 values(2,'bbb',0);
insert into t3 values(3,'ccc',0);
commit;create index ind_t3_id on t3(id);select * from t3;
测试update
update t3 set id=id+10 where id=1 or (id>=10 and state=0);
查看更新后结果
2. 直接但不等价的改写
根据前面select or的改写,最直接的想法就是把它改成union all + lnnvl形式,又因为update没有union all的语法,这里拆成了2个update。
update t3 set id=id+10 where id=1
-- union all
update t3 set id=id+10 where (id>=10 and state=0) and lnnvl(id=1);
这种写法在把id更新为固定值(比如100)时没有问题,但如果像上面更新成id+10或者时间字段更新成sysdate,第二条update就可能会重复更新数据。
3. 等价改写
可以改写为merge语句,把union all + lnnvl 格式语句放在using部分,比如这样
merge into t3 b
using (
select * from t3 where id=1
union all
select * from t3 where (id>=10 and state=0) and lnnvl(id=1)
) a
on (b.id=a.id)
when matched then
update set b.id = b.id+10;
但是一执行发现会报错
我们在之前的文章 Oracle 关联更新 update_Hehuyi_In的博客-CSDN博客 中学习过这种“更新关联字段的merge写法”,需要将关联字段改为rowid。这里因为只涉及t3一个表,直接在using部分加上rowid字段即可。
merge into t3 b
using (
select rowid as rid,t3.* from t3 where id=1
union all
select rowid as rid,t3.* from t3 where (id>=10 and state=0) and lnnvl(id=1)
) a
on (b.rowid=a.rid)
when matched then
update set b.id = b.id+10;
六、 关联条件的or改写
1. 构造测试表
我们再加两个测试表
create table t4(id int,name varchar2(20),state int);insert into t4 values(1,'ddd',0);
insert into t4 values(10,'eee',0);
insert into t4 values(20,'fff',0);
commit;create table t5(id int,name varchar2(20),state int);insert into t5 values(1,'ggg',0);
insert into t5 values(5,'hhh',0);
insert into t5 values(20,'iii',0);
commit;
2. 关联条件中用or
select t3.id,t4.id,t5.id from t3
full join t4
on t3.id = t4.id
left join t5
on t3.id = t5.id or t4.id = t5.id;
查看执行计划(11g版本),外层只能用nest loop join,全关联返回结果集大时效率低。
3. 改写方法
还是按照之前的思路,结果集大时,通过改写让它走hash join,这里巧妙地把or条件改为了nvl。
select t3.id,t4.id,t5.id from t3
full join t4
on t3.id = t4.id
left join t5
on nvl(t3.id,t4.id) = t5.id;
改写后的SQL走上了hash join
这里补充一下,在19c版本测试时,发现原SQL外层查询走的也不再是nest loop join,而是merge join,应该是优化器又增强了。
参考
Oracle 表关联、半关联、反关联_Hehuyi_In的博客-CSDN博客
时隔4年,重新分析并修正一个update SQL的优化方法
Oracle 关联更新 update_Hehuyi_In的博客-CSDN博客
《SQL写法与改写-第二期》
Oracle OR条件的优化与改写相关推荐
- Oracle 数据库的性能优化
oracle数据库的性能优化 对于ORACLE数据库的数据存取,主要有四个不同的调整级别,第一级调整是操作系统级包括硬件平台,第二级调整是ORACLE RDBMS级的调整,第三级是数据库设计级的调整, ...
- Oracle SQL语句性能优化方法大全
下面列举一些工作中常常会碰到的Oracle的SQL语句优化方法: 1.SQL语句尽量用大写的: 因为oracle总是先解析SQL语句,把小写的字母转换成大写的再执行. 2.选择最有效率的表名顺序(只在 ...
- oracle sql语句常用优化方法
oracle sql语句常用优化方法 最近做一些报表查询,经常做一些小优化,在这里总结一下 语句上的优化: 1.SELECT 语句中避免使用*,用那些字段就摘出哪些. 2.SQL语句尽量用大写: 因为 ...
- oracle千万行update优化,Oracle的update优化
Oracle的update语句优化研究 一. update语句的语法与原理 1. 语法 单表:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值如:update t_join ...
- oracle查询多个条件查询,ORACLE多条件统计查询的简单方法
前几天要做一个统计查询的功能,因为涉及多张表,多种条件的统计分析.一开始便想到了UNION和IF语句,然后写了1000多行代码,就为了查30条数据觉得不应该. @H_301_2@ 然后就开始百度,多种 ...
- Oracle 数据库表性能优化
Oracle 数据库表性能优化 最近在一次工作过程中,遇到了oralce 表性能慢的问题.一个历史表,一个月将近1000多万的数据量,想查询这个表的数据,只使用了一个简单的语句,却一个多小时都查不出来 ...
- oracle存储过程 多条件,Oracle多条件查询实际分页存储过程实操
以下的文章主要是介绍Oracle多条件查询分页存储过程,以下就是Oracle多条件查询分页存储过程具体方案的描述,希望在你今后的学习中会有所帮助.将业务逻辑放到Oracle中使得后台代码很精简,Ora ...
- SOAR SQL进行优化和改写的自动化工具
前言 SQL优化是程序开发中经常遇到的问题,尤其是在程序规模不断扩大的时候.SQL的好坏不仅制约着程序的规模,影响着用户的体验,甚至威胁着信息的安全. 我们经常听到说哪家平台挂了,哪家网站被黑了,但我 ...
- case when then else_啃食Oracle:条件分支表达式CASE
啃食Oracle:条件分支表达式CASE CASE表达式是条件分支表达式,类似于if - elsif -else条件分支语句.常见用法是在select的表达式列表中使用. 以下图示来自于官方文档 上图 ...
最新文章
- AS3.0函数定义的方法
- android检查新版本,详解Android Studio无法检测新版本问题解决
- Node.js执行系统命令
- zookeeper集群部署(分布式)
- 前端学习(2610):vuex实现删除
- 为什么有时优盘是只读模式_JS专题之严格模式
- 移动硬盘拒绝访问找到数据的法子
- mysql5.7更改数据库存储位置_mysql 5.7更改数据库的数据存储位置的解决方法
- python32位 最大内存_64位windows上的Python 32位内存限制
- matlab时域转换成频域_频域的弦,时域的箭(1)
- 【转】kubernetes 中 deployment 支持哪些键值
- 计算机一级考试基本操作是什么,计算机一级等级考试试题基本操作总结
- AT24C02/04/08 地址理解
- 各种最短路算法能够处理的情况(优劣比较,时间复杂度等)
- 企业级数据服务总线规划
- 利用tcp三次握手,使用awl伪装MAC地址进行多线程SYN洪水攻击
- Python:实现integer partition整数分区算法(附完整源码)
- 【python】global详解
- python 将输出打印到文件
- CityMaker学习教程11 创建和移动标签