【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% |

相关推荐
帧栈1 天前
开发避坑指南(31):Oracle 11g LISTAGG函数使用陷阱,缺失WITHIN子句解决方案
oracle
小马哥编程1 天前
【软考架构】第6章 数据库基本概念
数据库·oracle·架构
ALLSectorSorft2 天前
搭子交友 app 动态分享与打卡系统设计实现
java·服务器·数据库·人工智能·oracle·交友
码农阿豪3 天前
KingbaseES数据库增删改查操作分享
数据库·oracle
AwhiteV3 天前
利用图数据库高效解决 Text2sql 任务中表结构复杂时占用过多大模型上下文的问题
数据库·人工智能·自然语言处理·oracle·大模型·text2sql
张永清3 天前
性能测试中性能分析与调优学习大纲整理
性能测试·性能调优·性能分析
不羁。。4 天前
【撸靶笔记】第七关:GET - Dump into outfile - String
数据库·笔记·oracle
杨云龙UP4 天前
CentOS Linux 7 (Core)上部署Oracle 11g、19C RAC详细图文教程
数据库·oracle
我科绝伦(Huanhuan Zhou)5 天前
银河麒麟V10一键安装Oracle 11g脚本分享
数据库·oracle