今天我偶然发现了一个案例,我很容易将其作为一个简单的优化.
假设您有一个名为my_table列的表,my_column并且您编写了一个存储过程,如下所示:
procedure my_proc(a_value my_table.my_column%type := null) is
begin
for i in (
select another_column from my_table
where a_value is null or my_column = a_value)
loop
-- do something
end loop;
end;
Run Code Online (Sandbox Code Playgroud)
a_value is null为了select语句,我总是假设表达式是常量,或者纯粹由PL变量和其他常量组成的任何表达式.换句话说,可以在执行查询之前安全地评估它并替换常量.在此代码中,例如,当
a_value未传递时,查询将等效于
select another_column from my_table
Run Code Online (Sandbox Code Playgroud)
相反,当值传递时,查询将等效于
select another_column from my_table
where my_column = a_value
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是,没有进行这种简单的优化.该a_value is null
表达似乎得到了在表中的每个记录,并具有足够大的表来执行,所不同的是引人注意无需任何特殊工具.我正在使用版本10,并将此优化视为很久以前就已经完成的2.0或3.0功能.
显然,必须有一些原因.也许我认为PL变量是SQL查询眼睛常数的假设是不正确的.也许PL变量可以在执行SQL查询期间发生变化.你知道这种情况吗?
当Oracle需要编译和优化SQL查询时,它必须创建一个查询计划,无论绑定变量的值是什么,它都将起作用,因为该计划可以在相同的查询中重用,但以后可以使用不同的值.
在查询中,select another_column from my_table where a_value is null or my_column = a_valuea_value是绑定变量.该查询只会被硬解析为一次查询计划.它无法崩溃,select another_column from my_table因为下一次调用存储过程可能会传入非null的a_value.
编辑添加一个例子.
问汤姆有一个帖子处理这个问题的更复杂的版本.根据他的回答.
样本数据:
create table my_table(my_column varchar2(10) null
, another_column varchar2(10) null)
/
insert into my_table values('1', 'a');
insert into my_table values('2', 'b');
insert into my_table values('3', 'c');
insert into my_table values('4', 'd');
insert into my_table values('5', 'e');
insert into my_table values('6', 'f');
insert into my_table values('7', 'g');
insert into my_table values('8', 'h');
insert into my_table values('9', 'i');
insert into my_table values('10', 'j');
commit;
Run Code Online (Sandbox Code Playgroud)
程序,流程:
create or replace procedure my_proc(p_value in my_table.my_column%TYPE default null) is
type l_rec_type is record(another_column my_table.another_column%TYPE);
l_sql varchar2(32767) := 'select another_column from my_table where ';
l_cursor sys_refcursor;
l_record l_rec_type;
begin
if p_value is null then
l_sql := l_sql || '(1=1 or :my_column is null)';
else
l_sql := l_sql || '(my_column = :my_column)';
end if;
open l_cursor for l_sql using p_value;
loop
fetch l_cursor into l_record;
exit when l_cursor%NOTFOUND;
-- do something
dbms_output.put_line(l_record.another_column);
end loop;
close l_cursor;
end my_proc;
/
Run Code Online (Sandbox Code Playgroud)
观看它运行:
SQL> exec my_proc()
a
b
c
d
e
f
g
h
i
j
PL/SQL procedure successfully completed.
SQL> exec my_proc('2')
b
PL/SQL procedure successfully completed.
Run Code Online (Sandbox Code Playgroud)