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

相关推荐
Blossom.1188 小时前
从“能写”到“能干活”:大模型工具调用(Function-Calling)的工程化落地指南
数据库·人工智能·python·深度学习·机器学习·计算机视觉·oracle
不秃的开发媛9 小时前
Java连接池详解:从Oracle到TiDB的随缘之旅
java·oracle·tidb
嘻嘻哈哈曹先生9 小时前
Oracle数据库
数据库·oracle
0_0梅伊阁诗人11 小时前
Django ORM 模型
开发语言·数据库·笔记·python·oracle·django
数巨小码人13 小时前
Oracle SQL调优技巧实战指南
数据库·sql·oracle
noravinsc13 小时前
在银河麒麟v10上安装达梦8数据库
服务器·数据库·oracle
lu9up2 天前
因表并行引发的血案【故障处理案例】
数据库·oracle·dba
代码的余温2 天前
Oracle RAC共享存储核心技术
数据库·oracle
float_六七2 天前
数据库物理外键与逻辑外键全解析
数据库·oracle
大白的编程日记.2 天前
【MySQL】数据库的基本操作
数据库·mysql·oracle