【SQL优化案例】SQL改写 - 用 UNION ALL 替代 OR

1.问题SQL

原SQL:

sql 复制代码
SELECT OID, MNUM, ORDERID, DELEGATEID, TO_CHAR(GROUPID) GROUPID, SUBRECDEFID
FROM JUDGE_TASK
WHERE ORDERID = '171231208465729706'
   OR MNUM = '171231208465729706'
  AND CITY = 171;

执行计划走了 BITMAP CONVERSION TO ROWIDS,Oracle对 OR 条件做了位图转换,效率不是很高。

BITMAP CONVERSION FROM ROWIDS:对于普通 B*树索引,Oracle 也可以将数据记录的 ROWID 映射成一个位图,然后进行位图操作。

BITMAP CONVERSION TO ROWIDS:将位图映射为ROWID。在一个位图键值中,包含了一批数据记录的起始地址和结束地址,且这批记录是连续的,因此位图中的每一个位就按序对应了一条数据记录。

sql 复制代码
SQL> SELECT OID,MNUM, ORDERID, DELEGATEID ,TO_CHAR(GROUPID) GROUPID,
  2  SUBRECDEFID FROM JUDGE_TASK WHERE ORDERID = '314231016343245850'  OR MNUM = '314231016343245850'  AND
  3  CITY = 314;

1 row selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 534055191

------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |                       |     1 |    57 |   729   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE ALL                       |                       |     1 |    57 |   729   (0)| 00:00:01 |     1 |   364 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |   729   (0)| 00:00:01 |     1 |   364 |
|   3 |    BITMAP CONVERSION TO ROWIDS             |                       |       |       |            |          |       |       |
|   4 |     BITMAP OR                              |                       |       |       |            |          |       |       |
|   5 |      BITMAP CONVERSION FROM ROWIDS         |                       |       |       |            |          |       |       |
|*  6 |       INDEX RANGE SCAN                     | IDX_JUDGE_TASK_ORDERID |       |       |   292   (0)| 00:00:01 |     1 |   364 |
|   7 |      BITMAP CONVERSION FROM ROWIDS         |                       |       |       |            |          |       |       |
|*  8 |       INDEX RANGE SCAN                     | IDX_JUDGE_TASK_MNUM |       |       |   437   (0)| 00:00:01 |     1 |   364 |
------------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter("ORDERID"='314231016343245850' OR "MNUM"='314231016343245850' AND "CITY"=314)	<<<<
   6 - access("ORDERID"='314231016343245850')
   8 - access("MNUM"='314231016343245850')


Statistics
----------------------------------------------------------
          2  recursive calls
          0  db block gets
       1017  consistent gets
          0  physical reads
          0  redo size
        966  bytes sent via SQL*Net to client
        549  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

2.优化措施

核心思路 :用 UNION ALL 拆分 OR 条件,让每个子查询单独走索引,提高访问效率。

2.1 错误改写SQL

读SQL的逻辑可能是想从JUDGE_TASK表中取出ORDERID = '314231016343245850' OR MNUM = '314231016343245850'并且CITY=171的数据,该写如下:

sql 复制代码
SELECT OID, MNUM, ORDERID, DELEGATEID, TO_CHAR(GROUPID) GROUPID, SUBRECDEFID
FROM JUDGE_TASK
WHERE ORDERID = '171231208465729706'
  AND CITY = 171
UNION ALL
SELECT OID, MNUM, ORDERID, DELEGATEID, TO_CHAR(GROUPID) GROUPID, SUBRECDEFID
FROM JUDGE_TASK
WHERE MNUM = '171231208465729706'
  AND CITY = 171;


Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3419299962

-------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |                       |     2 |   114 |    60   (0)| 00:00:01 |       |       |
|   1 |  UNION-ALL                                  |                       |       |       |            |          |       |       |
|   2 |   PARTITION RANGE ITERATOR                  |                       |     1 |    57 |    24   (0)| 00:00:01 |   113 |   141 |
|*  3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |    24   (0)| 00:00:01 |   113 |   141 |
|*  4 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_ORDERID |     1 |       |    24   (0)| 00:00:01 |   113 |   141 |
|   5 |   PARTITION RANGE ITERATOR                  |                       |     1 |    57 |    36   (0)| 00:00:01 |   113 |   141 |
|*  6 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |    36   (0)| 00:00:01 |   113 |   141 |
|*  7 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_MNUM |     1 |       |    35   (0)| 00:00:01 |   113 |   141 |
-------------------------------------------------------------------------------------------------------------------------------------

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

   3 - filter("CITY"=314)
   4 - access("ORDERID"='314231016343245850') <<<<<<
   6 - filter("CITY"=314)
   7 - access("MNUM"='314231016343245850') <<<<<


Statistics
----------------------------------------------------------
          3  recursive calls
          0  db block gets
        158  consistent gets
          0  physical reads
          0  redo size
        966  bytes sent via SQL*Net to client
        769  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

该写完和原来SQL一对结果一致,逻辑读从1000降到了158,效果很好了。我在统计时还在计算优化效率

复制代码
逻辑读 由 1017 -> 158  

平均每10h 35亿个逻辑读,预计降低 (1017-158)/1017=84.4%

一天预计可从80亿减少到12.4亿
 80 * (1-84.4%)= 12.4亿

附:and 和 or 的优先级比较

过了一段时间这个SQL也没有实施,在OB上碰到一个查询转换的hint想拿来试试发现了问题,sql中and优先级好像比or高,证明一下

sql 复制代码
-- and 和 or的优先级比较
-- 反证法
-- 假设or的优先级比and高
a.
select 1 from dual where 1=1 or 1=1 and 1=2;-- 有结果

b.
select 1 from dual where 1=1 and 1=2 union ALL
select 1 from dual where 1=1 and 1=2; -- 无结果

c.
select 1 from dual where 1=1 union ALL
select 1 from dual where 1=1 and 1=2; -- 有结果

d.
select 1 from dual where (1=1 or 1=1) and 1=2; -- 无结果

e.
select 1 from dual where 1=1 or (1=1 and 1=2);-- 有结果

假设or的优先级比and高, 也就是说在没有括号的情况下,Oracle 会先计算 OR,再计算 AND

那么a成立那么d一定成立, 但实际返回结果,与假设矛盾,推到,得证 Oracle 中 AND 优先于 OR

这个好像很冷门问了很多同事都没了解过。

2.2 正确改写SQL

SQL因为可读性很差被误导了,实际上and优先级高于or,应该将ORDERID = '171231208465729706' AND CITY = 171 UNION ALL ... MNUM = '171231208465729706' AND CITY = 171;改成ORDERID = '171231208465729706' UNION ALL ... MNUM = '171231208465729706' AND CITY = 171;

sql 复制代码
SQL> SQL> SELECT OID, MNUM, ORDERID, DELEGATEID, TO_CHAR(GROUPID) AS GROUPID, SUBRECDEFID
  2  FROM TBCS.JUDGE_TASK
  3  WHERE ORDERID = '171231208465729706'
  4  UNION ALL
  5  SELECT OID, MNUM, ORDERID, DELEGATEID, TO_CHAR(GROUPID) AS GROUPID, SUBRECDEFID
  6  FROM TBCS.JUDGE_TASK
  7  WHERE MNUM = '171231208465729706' AND CITY = 171
  8    AND ORDERID <> '171231208465729706'; 
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------

Plan hash value: 1123629201

-------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |                       |     2 |   110 |   328   (0)| 00:00:01 |       |       |
|   1 |  UNION-ALL                                  |                       |       |       |            |          |       |       |
|   2 |   PARTITION RANGE ALL                       |                       |     1 |    53 |   292   (0)| 00:00:01 |     1 |   364 |
|   3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    53 |   292   (0)| 00:00:01 |     1 |   364 |
|*  4 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_ORDERID |     1 |       |   292   (0)| 00:00:01 |     1 |   364 |
|   5 |   PARTITION RANGE ITERATOR                  |                       |     1 |    57 |    36   (0)| 00:00:01 |   169 |   197 |
|*  6 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |    36   (0)| 00:00:01 |   169 |   197 |
|*  7 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_MNUM |     1 |       |    35   (0)| 00:00:01 |   169 |   197 |
-------------------------------------------------------------------------------------------------------------------------------------

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

   4 - access("ORDERID"='171231208465729706')
   6 - filter("CITY"=171 AND "ORDERID"<>'171231208465729706')
   7 - access("MNUM"='171231208465729706')


Statistics
----------------------------------------------------------
          2  recursive calls
          0  db block gets
        609  consistent gets
          0  physical reads
         76  redo size
        984  bytes sent via SQL*Net to client
        714  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

逻辑读 由 1017 -> 609  
平均每10h 35亿个逻辑读,预计降低 (1017-609)/1017=40.1%

一天预计可从80亿减少到47.92亿
 80 * (1-40.1%)= 47.92亿

2.3 HINT 改写 - USE_CONCAT

其实可以不用改写SQL,HINT中提供了一个/*+ USE_CONCAT */,强制优化器使用 UNION ALL 运算符将 OR 条件转换为复合查询。

sql 复制代码
SQL> SELECT/*+ USE_CONCAT */ OID,
  2         MNUM,
  3         ORDERID,
  4         DELEGATEID,
  5         TO_CHAR(GROUPID) GROUPID,
  6         SUBRECDEFID
  7    FROM JUDGE_TASK
  8   WHERE ORDERID = '171231208465729706'
  9      OR MNUM = '171231208465729706'
 10     AND CITY = 171;

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 46570055

-------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |                       |     2 |   114 |   328   (0)| 00:00:01 |       |       |
|   1 |  CONCATENATION                              |                       |       |       |            |          |       |       |
|   2 |   PARTITION RANGE ITERATOR                  |                       |     1 |    57 |    36   (0)| 00:00:01 |   169 |   197 |
|*  3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |    36   (0)| 00:00:01 |   169 |   197 |
|*  4 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_MNUM |     1 |       |    35   (0)| 00:00:01 |   169 |   197 |
|   5 |   PARTITION RANGE ALL                       |                       |     1 |    57 |   292   (0)| 00:00:01 |     1 |   364 |
|*  6 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| JUDGE_TASK             |     1 |    57 |   292   (0)| 00:00:01 |     1 |   364 |
|*  7 |     INDEX RANGE SCAN                        | IDX_JUDGE_TASK_ORDERID |     1 |       |   292   (0)| 00:00:01 |     1 |   364 |
-------------------------------------------------------------------------------------------------------------------------------------

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

   3 - filter("CITY"=171)
   4 - access("MNUM"='171231208465729706')
   6 - filter(LNNVL("MNUM"='171231208465729706') OR LNNVL("CITY"=171))
   7 - access("ORDERID"='171231208465729706')


Statistics
----------------------------------------------------------
          2  recursive calls
          0  db block gets
        614  consistent gets
          0  physical reads
          0  redo size
        984  bytes sent via SQL*Net to client
        606  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

3.优化效果对比

原逻辑读:80亿/天,优化后(40%降低):80 * (1-0.401) ≈ 47.92亿

|-----------------|------|-----------------------------------|
| SQL | 逻辑读 | 优化方法 |
| 原SQL | 1017 | BITMAP CONVERSION TO ROWIDS,OR条件慢 |
| or 改成 UNION ALL | 609 | 走索引范围扫描,效率提升40% |

相关推荐
ClouGence1 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
曹牧2 天前
Oracle EXPLAIN PLAN
数据库·oracle
贤时间2 天前
codex 助力oracle ebs 开发
数据库·oracle
秉承初心2 天前
PostgreSQL 数据性能瓶颈突破实战
数据库·postgresql·oracle
Curvatureflight2 天前
MySQL 深分页越来越慢?从 LIMIT OFFSET 改成游标分页
数据库·oracle
XZ-0700012 天前
MySQL事务
数据库·mysql·oracle
tiancaijiben2 天前
阿里云函数计算FC如何实现网站的定时任务与自动化
数据库·oracle·dba
xfhuangfu2 天前
Oracle 19c 多租户体系架构介绍
数据库·oracle·架构
杨云龙UP2 天前
Spotlight 接入 Oracle 数据库监控操作指南 2026-06-16
数据库·oracle·性能监控·预警·阈值·spotlight·瓶颈分析
unique2 天前
AI Coding 采集方案探索
jvm·人工智能·oracle