【发布时间】:2021-07-15 18:52:06
【问题描述】:
我对 Oracle 非常缺乏经验。这是怎么回事?
查询 A:
SELECT COUNT(*)
FROM MUHSCHEMA.MUH_TABLE
WHERE MUH_DATE = TO_DATE(
TRIM(
'''' FROM SYS.DBMS_ASSERT.ENQUOTE_LITERAL('09/30/2020')),
'mm/dd/yyyy'
);
查询 B:
SELECT COUNT(*)
FROM MUHSCHEMA.MUH_TABLE
WHERE MUH_DATE = TO_DATE('09/30/2020', 'mm/dd/yyyy');
查询 A 大约需要 22 分钟。查询 B 大约需要 28 秒。而且,看起来,带有或不带有 ENQUOTE_LITERAL 的 TO_DATE 调用都返回相同的内容。
为什么查询 A 需要这么长时间?
查询计划:
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 9 | 411K (2)| 00:00:17 | | |
| 1 | SORT AGGREGATE | | 1 | 9 | | | | |
| 2 | VIEW | A_TABLE | 71M| 610M| 411K (2)| 00:00:17 | | |
| 3 | UNION-ALL | | | | | | | |
| 4 | PARTITION RANGE ALL | | 28M| 214M| 42669 (15)| 00:00:02 | 1 |1048575|
| 5 | PARTITION LIST ALL | | 28M| 214M| 42669 (15)| 00:00:02 | 1 | 25 |
|* 6 | INDEX FAST FULL SCAN| A_TABLE. | 28M| 214M| 42669 (15)| 00:00:02 | 1 |1048575|
| 7 | PARTITION RANGE ALL | | 42M| 327M| 368K (1)| 00:00:15 | 1 |1048575|
| 8 | PARTITION LIST ALL | | 42M| 327M| 368K (1)| 00:00:15 | 1 | 25 |
|* 9 | INDEX RANGE SCAN | A_TABLE. | 42M| 327M| 368K (1)| 00:00:15 | 1 |1048575|
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 6 - filter(""MUH_DATE""=TO_DATE(TRIM('''' FROM ""DBMS_ASSERT"".""ENQUOTE_LITERAL""('09/30/2020')),'mm/dd/yy"
yy'))
" 9 - access(""MUH_DATE""=TO_DATE(TRIM('''' FROM ""DBMS_ASSERT"".""ENQUOTE_LITERAL""('09/30/2020')),'mm/dd/yy"
yy'))
查询 B 计划:
----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 9 | 36612 (1)| 00:00:02 | | |
| 1 | SORT AGGREGATE | | 1 | 9 | | | | |
| 2 | VIEW | A_TABLE. | 28M| 241M| 36612 (1)| 00:00:02 | | |
| 3 | UNION-ALL | | | | | | | |
| 4 | PARTITION RANGE SINGLE| | 28M| 214M| 36608 (1)| 00:00:02 | 250 | 250 |
| 5 | PARTITION LIST ALL | | 28M| 214M| 36608 (1)| 00:00:02 | 1 | 25 |
|* 6 | INDEX FAST FULL SCAN| A_TABLE | 28M| 214M| 36608 (1)| 00:00:02 | 6226 | 6250 |
| 7 | PARTITION RANGE SINGLE| | 1 | 8 | 4 (0)| 00:00:01 | 93 | 93 |
| 8 | PARTITION LIST ALL | | 1 | 8 | 4 (0)| 00:00:01 | 1 | 25 |
|* 9 | INDEX RANGE SCAN | A_TABLE. | 1 | 8 | 4 (0)| 00:00:01 | 2301 | 2325 |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 6 - filter(""MUH_DATE""=TO_DATE(' 2020-09-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))"
" 9 - access(""MUH_DATE""=TO_DATE(' 2020-09-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))"
【问题讨论】:
-
顺便说一句,您可能已经意识到,但这并不是保护此类查询免受 SQL 注入影响的最佳方法 - 您通常会改用绑定变量,这有额外的好处(减少对查询的解析,不会用类似的查询淹没 SGA,等等)
-
它看起来很像
enquote_literal,并且正在为表中的每一行重复转换为日期;我可以在具有未索引日期列的大型(非分区)表上复制效果 - 添加索引似乎可以治愈它,并且也会加快 28 年代版本的速度。或者您可以在物化 CTE 中转换一次值,但这似乎有点混乱。正如 Boneist 所说,如果可能的话,最好传入一个已经是日期的绑定值。 -
我不明白的部分是为什么您希望通过查询中的硬编码文字来实现 SQL 注入。实际上,您是否在那个地方使用了替换变量?如果是,Oracle 无法知道运行时的值在每一行中都相同(SQL 不理解 SQL*Plus 脚本语言,即使它理解了,您的查询也应该使用
&&表示法以表明该变量在任何地方都获得相同的文字值)。我要说的是:如果那是您的确切查询,那么您为什么要那样做?如果不同,请向我们展示真实的。 -
@steamrolla - 我也是……我的猜测是,如果没有索引,它总是会进行全表扫描,而它会提前评估日期以确定是否可以进行索引全扫描或范围扫描。在您的示例中,分区修剪可能会发生类似的事情。不确定这是否真的有意义;除了现在该计划有一个访问谓词,而不是过滤谓词 - 所以可能对这些进行不同的评估。 (虽然听起来仍然有点像一个错误,所以可能值得向 Oracle 询问。)
-
似乎在“partition range all”和“partition range single”处发生了变化。这表明 trim..enquote 会阻止 QP 理解/应用修剪 — docs.oracle.com/database/121/VLDBG/…
标签: sql oracle sqlperformance oracle19c