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