Oracle 11gR2 - 查看函数列评估
Oracle 11gR2 - View Function Columns Evaluation
我似乎有一个关于 Oracle 视图的奇怪问题,该视图具有为列定义的函数以及何时评估这些函数。
假设我有以下视图和函数定义:
CREATE OR REPLACE VIEW test_view_one AS
SELECT column_one,
a_package.function_that_returns_a_value(column_one) function_column
FROM a_table;
CREATE OR REPLACE PACKAGE BODY a_package AS
FUNCTION function_that_returns_a_value(p_key VARCHAR2) RETURN VARCHAR2 IS
CURSOR a_cur IS
SELECT value
FROM table_b
WHERE key = p_key;
p_temp VARCHAR2(30);
BEGIN
-- Code here to write into a temp table. The function call is autonomous.
OPEN a_cur;
FETCH a_cur INTO p_temp;
CLOSE a_cur;
RETURN p_temp;
END function_that_returns_a_value;
END a_package;
一般来说,如果 function_column 包含在查询中,那么对于该查询返回的每一行,函数将是 运行。这在某些情况下似乎是正确的,但在其他情况下则不然。
例如,假设我有以下内容:
SELECT pageouter,*
FROM(WITH page_query AS (SELECT *
FROM test_view_one
ORDER BY column_one)
SELECT page_query.*, ROWNUM as innerrownum
FROM page_query
WHERE rownum <= 25) pageouter WHERE pageouter.innerrownum >= 1
在这种情况下,该内部查询(查询 test_view_one 的查询)带回了大约 90,000 条记录。
如果我将函数定义为插入到临时 table 中,那么我可以告诉函数 运行 25 次,每次返回的每一行。正是我所期望的。
但是,如果我在该内部查询上添加一个重要的 where 子句,例如
SELECT pageouter,*
FROM(WITH page_query AS (SELECT *
FROM test_view_one
WHERE EXISTS (SELECT 'x' FROM some_table WHERE ...)
AND NOT EXISTS (SELECT 'x' FROM some_other_table WHERE ...)
AND EXISTS (SELECT 'x' FROM another_table WHERE ...)
ORDER BY column_one)
SELECT page_query.*, ROWNUM as innerrownum
FROM page_query
WHERE rownum <= 25) pageouter WHERE pageouter.innerrownum >= 1
然后内部查询带回的行数是 60,000,如果我然后查询临时 table,我可以告诉函数有 运行 60,000 次。毫不奇怪,这几乎会破坏查询的性能。
上面的查询是 运行 作为分页实现的一部分,这就是为什么我们只带回 25 行,这就是为什么我们只需要对这 25 行的函数 运行 .
我应该补充一点,如果我更改了 WHERE 子句(即我删除了一些条件),那么查询将返回到它自己的行为,仅 运行 为 25 行实际带回来了。
有人知道什么时候评估视图中的函数吗?或者无论如何确定导致它的原因或确定函数何时被评估的方法(我已经检查了解释计划并且那里没有任何东西似乎可以泄露它)。如果我知道这一点,那么我就有希望找到问题的解决方案,但除了 "They'll run for each row brought back" 之外似乎没有什么文档,这在某些情况下显然不是这种情况。
我完全理解如果没有工作模式很难弄清楚发生了什么,但如果您需要更多信息,请随时询问。
非常感谢
根据要求提供其他信息。
以下是我从生产环境中得到的实际解释方案。 table 名称与上面的查询不匹配(事实上,涉及的 tables 相当多,但它们都由 WHERE 子句中的 NOT EXISTS 语句连接。)
DEMISE table 相当于上述查询中的 A_TABLE。
值得注意的是,统计数据是在我 运行 解释计划之前收集的,以使其尽可能准确。
我对此的理解是,VIEW 行是计算函数的位置,它发生在行被过滤掉之后。我的理解明显有问题!
所以这是糟糕的计划,调用函数 60,000 次的计划...
Execution Plan
----------------------------------------------------------
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 10230 | 984 (1)|
| 1 | FAST DUAL | | 1 | | 2 (0)|
| 2 | FAST DUAL | | 1 | | 2 (0)|
|* 3 | VIEW | | 5 | 10230 | 984 (1)|
|* 4 | COUNT STOPKEY | | | | |
| 5 | VIEW | | 5 | 10165 | 984 (1)|
|* 6 | SORT ORDER BY STOPKEY | | 5 | 340 | 984 (1)|
| 7 | COUNT | | | | |
|* 8 | FILTER | | | | |
|* 9 | HASH JOIN RIGHT OUTER | | 5666 | 376K| 767 (1)|
|* 10 | INDEX RANGE SCAN | USERDATAI1 | 1 | 12 | 2 (0)|
|* 11 | HASH JOIN RIGHT ANTI | | 5666 | 309K| 765 (1)|
|* 12 | INDEX FAST FULL SCAN | TNNTMVINI1 | 1 | 17 | 35 (0)|
|* 13 | HASH JOIN RIGHT ANTI | | 6204 | 236K| 729 (1)|
|* 14 | INDEX RANGE SCAN | CODESGENI3 | 1 | 10 | 2 (0)|
|* 15 | INDEX FULL SCAN | DEMISEI4 | 6514 | 184K| 727 (1)|
| 16 | NESTED LOOPS | | 1 | 25 | 3 (0)|
| 17 | NESTED LOOPS | | 1 | 25 | 3 (0)|
|* 18 | INDEX RANGE SCAN | PROPERTY_GC | 1 | 15 | 2 (0)|
|* 19 | INDEX UNIQUE SCAN | CODESGENI1 | 1 | | 0 (0)|
|* 20 | TABLE ACCESS BY INDEX ROWID| CODESGEN | 1 | 10 | 1 (0)|
| 21 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
|* 22 | INDEX RANGE SCAN | DMSELEASI4 | 1 | 21 | 2 (0)|
|* 23 | INDEX RANGE SCAN | TNNTMVINI1 | 1 | 17 | 1 (0)|
| 24 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
-------------------------------------------------------------------------------------------
这是好的计划。这调用了该函数 25 次,但从 where 子句中删除了一些不存在的语句。
Execution Plan
----------------------------------------------------------
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 54200 | 144 (0)|
| 1 | FAST DUAL | | 1 | | 2 (0)|
| 2 | FAST DUAL | | 1 | | 2 (0)|
|* 3 | VIEW | | 25 | 54200 | 144 (0)|
|* 4 | COUNT STOPKEY | | | | |
| 5 | VIEW | | 26 | 56030 | 144 (0)|
| 6 | COUNT | | | | |
|* 7 | FILTER | | | | |
| 8 | NESTED LOOPS ANTI | | 30 | 3210 | 144 (0)|
| 9 | NESTED LOOPS OUTER | | 30 | 2580 | 114 (0)|
| 10 | NESTED LOOPS ANTI | | 30 | 2220 | 84 (0)|
| 11 | NESTED LOOPS ANTI | | 32 | 1824 | 52 (0)|
| 12 | TABLE ACCESS BY INDEX ROWID| DEMISE | 130K| 5979K| 18 (0)|
| 13 | INDEX FULL SCAN | DEMISEI4 | 34 | | 3 (0)|
|* 14 | INDEX RANGE SCAN | CODESGENI3 | 1 | 10 | 1 (0)|
|* 15 | INDEX RANGE SCAN | TNNTMVINI1 | 1 | 17 | 1 (0)|
|* 16 | INDEX RANGE SCAN | USERDATAI1 | 1 | 12 | 1 (0)|
|* 17 | INDEX RANGE SCAN | DMSELEASI4 | 1 | 21 | 1 (0)|
| 18 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
----------------------------------------------------------------------------------------
我非常感谢第二个计划做的更少,但这并不能解释为什么没有评估这些功能……至少我不能解决。
可以执行Pagination with ROWNUM
两种方式:
A) 使用优化排序全面扫描行源(限于前 N 行)或
B) 完全没有排序的行源的索引访问
此处是 案例 A
的简化示例
SELECT *
FROM
(SELECT a.*,
ROWNUM rnum
FROM
( SELECT * FROM test_view_one ORDER BY id
) a
WHERE ROWNUM <= 25
)
WHERE rnum >= 1
对应的执行计划如下(注意我也presend了一部分
列投影 - 我将很快解释原因):
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 975 | | 1034 (1)| 00:00:01 |
|* 1 | VIEW | | 25 | 975 | | 1034 (1)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 90000 | 2285K| | 1034 (1)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY| | 90000 | 439K| 1072K| 1034 (1)| 00:00:01 |
| 5 | TABLE ACCESS FULL | TEST | 90000 | 439K| | 756 (1)| 00:00:01 |
-----------------------------------------------------------------------------------------
Column Projection Information (identified by operation id):
-----------------------------------------------------------
...
3 - "A"."ID"[NUMBER,22], "A"."FUNCTION_COLUMN"[NUMBER,22]
4 - (#keys=1) "ID"[NUMBER,22], "MY_PACKAGE"."MY_FUNCTION"("ID")[22]
5 - "ID"[NUMBER,22]
在执行过程中,table 是通过全扫描访问的,即所有记录都是红色的。
优化在SORT操作中:SORT ORDER BY STOPKEY表示不是所有
行已排序,但仅保留和排序前 25 行。
这里是 案例 B
的执行计划
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 975 | 2 (0)| 00:00:01 |
|* 1 | VIEW | | 25 | 975 | 2 (0)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 26 | 676 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| TEST_IDX | 26 | 130 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------
此处仅访问了所需的 25 行,因此该函数不能被调用超过 N 次。
现在重要的考虑因素是,在情况 A 中,可以但不需要为每一行调用该函数。我们怎么看?
答案在解释计划中的投影列中。
4 - (#keys=1) "ID"[NUMBER,22], "MY_PACKAGE"."MY_FUNCTION"("ID")[22]
相关的第 4 行显示,该函数在 SORT 操作中被调用,因此每一行都被调用。 (排序获取所有行)。
结论
我在 11.2 上的测试表明,在情况 A(使用 SORT ORDER BY STOPKEY 的全扫描)中,视图函数被调用
每行一次。
我想唯一的解决方法是 select 只有 ID,限制结果,然后加入原始视图以获取函数值。
最后的笔记
我也在 12.1 中对此进行了测试,并在下面看到了列投影中的偏移。
该函数首先在 VIEW 中计算(第 3 行),即两种情况都可以正常工作。
Column Projection Information (identified by operation id):
-----------------------------------------------------------
...
3 - "A"."ID"[NUMBER,22], "A"."FUNCTION_COLUMN"[NUMBER,22]
4 - (#keys=1) "ID"[NUMBER,22]
5 - "ID"[NUMBER,22]
当然在 12c 中可以使用 OFFSET 的新功能 - FETCH NEXT。
祝你好运!
我似乎有一个关于 Oracle 视图的奇怪问题,该视图具有为列定义的函数以及何时评估这些函数。
假设我有以下视图和函数定义:
CREATE OR REPLACE VIEW test_view_one AS
SELECT column_one,
a_package.function_that_returns_a_value(column_one) function_column
FROM a_table;
CREATE OR REPLACE PACKAGE BODY a_package AS
FUNCTION function_that_returns_a_value(p_key VARCHAR2) RETURN VARCHAR2 IS
CURSOR a_cur IS
SELECT value
FROM table_b
WHERE key = p_key;
p_temp VARCHAR2(30);
BEGIN
-- Code here to write into a temp table. The function call is autonomous.
OPEN a_cur;
FETCH a_cur INTO p_temp;
CLOSE a_cur;
RETURN p_temp;
END function_that_returns_a_value;
END a_package;
一般来说,如果 function_column 包含在查询中,那么对于该查询返回的每一行,函数将是 运行。这在某些情况下似乎是正确的,但在其他情况下则不然。
例如,假设我有以下内容:
SELECT pageouter,*
FROM(WITH page_query AS (SELECT *
FROM test_view_one
ORDER BY column_one)
SELECT page_query.*, ROWNUM as innerrownum
FROM page_query
WHERE rownum <= 25) pageouter WHERE pageouter.innerrownum >= 1
在这种情况下,该内部查询(查询 test_view_one 的查询)带回了大约 90,000 条记录。 如果我将函数定义为插入到临时 table 中,那么我可以告诉函数 运行 25 次,每次返回的每一行。正是我所期望的。
但是,如果我在该内部查询上添加一个重要的 where 子句,例如
SELECT pageouter,*
FROM(WITH page_query AS (SELECT *
FROM test_view_one
WHERE EXISTS (SELECT 'x' FROM some_table WHERE ...)
AND NOT EXISTS (SELECT 'x' FROM some_other_table WHERE ...)
AND EXISTS (SELECT 'x' FROM another_table WHERE ...)
ORDER BY column_one)
SELECT page_query.*, ROWNUM as innerrownum
FROM page_query
WHERE rownum <= 25) pageouter WHERE pageouter.innerrownum >= 1
然后内部查询带回的行数是 60,000,如果我然后查询临时 table,我可以告诉函数有 运行 60,000 次。毫不奇怪,这几乎会破坏查询的性能。
上面的查询是 运行 作为分页实现的一部分,这就是为什么我们只带回 25 行,这就是为什么我们只需要对这 25 行的函数 运行 .
我应该补充一点,如果我更改了 WHERE 子句(即我删除了一些条件),那么查询将返回到它自己的行为,仅 运行 为 25 行实际带回来了。
有人知道什么时候评估视图中的函数吗?或者无论如何确定导致它的原因或确定函数何时被评估的方法(我已经检查了解释计划并且那里没有任何东西似乎可以泄露它)。如果我知道这一点,那么我就有希望找到问题的解决方案,但除了 "They'll run for each row brought back" 之外似乎没有什么文档,这在某些情况下显然不是这种情况。
我完全理解如果没有工作模式很难弄清楚发生了什么,但如果您需要更多信息,请随时询问。
非常感谢
根据要求提供其他信息。
以下是我从生产环境中得到的实际解释方案。 table 名称与上面的查询不匹配(事实上,涉及的 tables 相当多,但它们都由 WHERE 子句中的 NOT EXISTS 语句连接。) DEMISE table 相当于上述查询中的 A_TABLE。
值得注意的是,统计数据是在我 运行 解释计划之前收集的,以使其尽可能准确。
我对此的理解是,VIEW 行是计算函数的位置,它发生在行被过滤掉之后。我的理解明显有问题!
所以这是糟糕的计划,调用函数 60,000 次的计划...
Execution Plan
----------------------------------------------------------
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 10230 | 984 (1)|
| 1 | FAST DUAL | | 1 | | 2 (0)|
| 2 | FAST DUAL | | 1 | | 2 (0)|
|* 3 | VIEW | | 5 | 10230 | 984 (1)|
|* 4 | COUNT STOPKEY | | | | |
| 5 | VIEW | | 5 | 10165 | 984 (1)|
|* 6 | SORT ORDER BY STOPKEY | | 5 | 340 | 984 (1)|
| 7 | COUNT | | | | |
|* 8 | FILTER | | | | |
|* 9 | HASH JOIN RIGHT OUTER | | 5666 | 376K| 767 (1)|
|* 10 | INDEX RANGE SCAN | USERDATAI1 | 1 | 12 | 2 (0)|
|* 11 | HASH JOIN RIGHT ANTI | | 5666 | 309K| 765 (1)|
|* 12 | INDEX FAST FULL SCAN | TNNTMVINI1 | 1 | 17 | 35 (0)|
|* 13 | HASH JOIN RIGHT ANTI | | 6204 | 236K| 729 (1)|
|* 14 | INDEX RANGE SCAN | CODESGENI3 | 1 | 10 | 2 (0)|
|* 15 | INDEX FULL SCAN | DEMISEI4 | 6514 | 184K| 727 (1)|
| 16 | NESTED LOOPS | | 1 | 25 | 3 (0)|
| 17 | NESTED LOOPS | | 1 | 25 | 3 (0)|
|* 18 | INDEX RANGE SCAN | PROPERTY_GC | 1 | 15 | 2 (0)|
|* 19 | INDEX UNIQUE SCAN | CODESGENI1 | 1 | | 0 (0)|
|* 20 | TABLE ACCESS BY INDEX ROWID| CODESGEN | 1 | 10 | 1 (0)|
| 21 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
|* 22 | INDEX RANGE SCAN | DMSELEASI4 | 1 | 21 | 2 (0)|
|* 23 | INDEX RANGE SCAN | TNNTMVINI1 | 1 | 17 | 1 (0)|
| 24 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
-------------------------------------------------------------------------------------------
这是好的计划。这调用了该函数 25 次,但从 where 子句中删除了一些不存在的语句。
Execution Plan
----------------------------------------------------------
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 54200 | 144 (0)|
| 1 | FAST DUAL | | 1 | | 2 (0)|
| 2 | FAST DUAL | | 1 | | 2 (0)|
|* 3 | VIEW | | 25 | 54200 | 144 (0)|
|* 4 | COUNT STOPKEY | | | | |
| 5 | VIEW | | 26 | 56030 | 144 (0)|
| 6 | COUNT | | | | |
|* 7 | FILTER | | | | |
| 8 | NESTED LOOPS ANTI | | 30 | 3210 | 144 (0)|
| 9 | NESTED LOOPS OUTER | | 30 | 2580 | 114 (0)|
| 10 | NESTED LOOPS ANTI | | 30 | 2220 | 84 (0)|
| 11 | NESTED LOOPS ANTI | | 32 | 1824 | 52 (0)|
| 12 | TABLE ACCESS BY INDEX ROWID| DEMISE | 130K| 5979K| 18 (0)|
| 13 | INDEX FULL SCAN | DEMISEI4 | 34 | | 3 (0)|
|* 14 | INDEX RANGE SCAN | CODESGENI3 | 1 | 10 | 1 (0)|
|* 15 | INDEX RANGE SCAN | TNNTMVINI1 | 1 | 17 | 1 (0)|
|* 16 | INDEX RANGE SCAN | USERDATAI1 | 1 | 12 | 1 (0)|
|* 17 | INDEX RANGE SCAN | DMSELEASI4 | 1 | 21 | 1 (0)|
| 18 | TABLE ACCESS FULL | QCDUAL | 1 | | 3 (0)|
----------------------------------------------------------------------------------------
我非常感谢第二个计划做的更少,但这并不能解释为什么没有评估这些功能……至少我不能解决。
可以执行Pagination with ROWNUM 两种方式:
A) 使用优化排序全面扫描行源(限于前 N 行)或
B) 完全没有排序的行源的索引访问
此处是 案例 A
的简化示例 SELECT *
FROM
(SELECT a.*,
ROWNUM rnum
FROM
( SELECT * FROM test_view_one ORDER BY id
) a
WHERE ROWNUM <= 25
)
WHERE rnum >= 1
对应的执行计划如下(注意我也presend了一部分 列投影 - 我将很快解释原因):
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 975 | | 1034 (1)| 00:00:01 |
|* 1 | VIEW | | 25 | 975 | | 1034 (1)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 90000 | 2285K| | 1034 (1)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY| | 90000 | 439K| 1072K| 1034 (1)| 00:00:01 |
| 5 | TABLE ACCESS FULL | TEST | 90000 | 439K| | 756 (1)| 00:00:01 |
-----------------------------------------------------------------------------------------
Column Projection Information (identified by operation id):
-----------------------------------------------------------
...
3 - "A"."ID"[NUMBER,22], "A"."FUNCTION_COLUMN"[NUMBER,22]
4 - (#keys=1) "ID"[NUMBER,22], "MY_PACKAGE"."MY_FUNCTION"("ID")[22]
5 - "ID"[NUMBER,22]
在执行过程中,table 是通过全扫描访问的,即所有记录都是红色的。 优化在SORT操作中:SORT ORDER BY STOPKEY表示不是所有 行已排序,但仅保留和排序前 25 行。
这里是 案例 B
的执行计划 --------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 975 | 2 (0)| 00:00:01 |
|* 1 | VIEW | | 25 | 975 | 2 (0)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 26 | 676 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| TEST_IDX | 26 | 130 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------
此处仅访问了所需的 25 行,因此该函数不能被调用超过 N 次。
现在重要的考虑因素是,在情况 A 中,可以但不需要为每一行调用该函数。我们怎么看?
答案在解释计划中的投影列中。
4 - (#keys=1) "ID"[NUMBER,22], "MY_PACKAGE"."MY_FUNCTION"("ID")[22]
相关的第 4 行显示,该函数在 SORT 操作中被调用,因此每一行都被调用。 (排序获取所有行)。
结论
我在 11.2 上的测试表明,在情况 A(使用 SORT ORDER BY STOPKEY 的全扫描)中,视图函数被调用 每行一次。 我想唯一的解决方法是 select 只有 ID,限制结果,然后加入原始视图以获取函数值。
最后的笔记
我也在 12.1 中对此进行了测试,并在下面看到了列投影中的偏移。 该函数首先在 VIEW 中计算(第 3 行),即两种情况都可以正常工作。
Column Projection Information (identified by operation id):
-----------------------------------------------------------
...
3 - "A"."ID"[NUMBER,22], "A"."FUNCTION_COLUMN"[NUMBER,22]
4 - (#keys=1) "ID"[NUMBER,22]
5 - "ID"[NUMBER,22]
当然在 12c 中可以使用 OFFSET 的新功能 - FETCH NEXT。
祝你好运!