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

相关推荐
jnrjian4 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle
TTc_4 天前
oracle中的union和union all有什么区别?
数据库·oracle
山峰哥4 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器
南 阳4 天前
Python从入门到精通day37
数据库·python·oracle
轩情吖4 天前
MySQL库的操作
android·数据库·mysql·oracle·字符集·数据库操作·编码集
脱发的老袁4 天前
【数据库】Oracle手动清理归档日志
数据库·oracle
jnrjian4 天前
Oracle 共享池 库缓存下的 Library Cache Lock
数据库·缓存·oracle
新缸中之脑5 天前
在Reddit上探索未满足的需求
数据库·oracle
light blue bird5 天前
产线多并发客户端指令操作场景组件
jvm·oracle·.net·winform
坐吃山猪5 天前
Neo4j04_数据库事务
数据库·oracle·neo4j