交易中快速变化的统计数据 - 固定执行计划
Statistics rapidly changing within transaction - fixing execution plan
我面临的一个问题(Oracle 11g):
我创建了一个 table,我们称之为 table_xyz,带有索引(不是唯一的,没有主键)。
我创建了一个程序包,假设每月插入 1000 万条记录——这不是简单的“插入”,它是数千行程序代码,其中一些实际上还从 table_xyz 中选择数据来计算要插入的数据进一步。
例如在过程中某处有这个查询
现在,有一个问题:
当程序是第一次 运行 时,table_xyz 上的所有查询都将根据 table_xyz 中有 0 条记录的时刻执行计划。
因此,所有查询都将有效地全面扫描 table_xyz,而不是在某个时候开始使用索引。
这会导致糟糕的性能,实际上在我的情况下,第一个 运行 永远不会完成...
现在,我想到了三种方法:
- 在事务中的某个时刻,重新计算统计信息。例如运行“analyze table / analyze index compute statistics”在table_xyz的记录数达到10次方后。
然而,这是不可能的,因为 ANALYZE 提交事务,我不能允许
- 在事务中的某个时刻,如上所述重新计算统计信息,但在自主事务中进行。然而,这不起作用,因为新的统计信息对于主事务不可见(我测试过)。
- 只需提示所有使用table_xyz的游标和USE_INDEX。这样就完成了工作,但是很丑陋并且在代码库中通常不受欢迎。
还有其他方法吗?
我附上一些代码。这只是一个例子,请不要尝试通过删除程序等方式优化它。
create table table_xyz (idx number(10) /*+ Specifically this is NOT a primary key */
,some_value varchar2(10)
);
create index table_xyz_idx on table_xyz (idx);
declare
cursor idxes is
select level idx
from dual d
connect by level < 100000;
current_val varchar2(10);
function calculate_some_value(p_idx number) return varchar2
is
cursor c_previous is
select t.some_value
from table_xyz t
where t.idx in (round(p_idx / 2, 0), round(p_idx / 3, 0), round(p_idx / 5, 0))
order by t.idx desc
;
x varchar2(100);
begin
open c_previous;
fetch c_previous into x;
close c_previous;
x := nvl(x, 'XYZ');
if mod(p_idx, 2) = 0 then
x := x || '2';
elsif mod(p_idx, 3) = 0 then
x := '3' || x;
elsif mod(p_idx, 5) = 0 then
x := substr(x, 1,1) || '5' || substr(x, 2, 2 + mod(p_idx, 7));
end if;
x := substr(x, 1, 10);
return x;
end calculate_some_value;
begin
for idx in idxes
loop
current_val := calculate_some_value(idx.idx);
insert into table_xyz(idx, some_value) values (idx.idx, current_val);
end loop;
end;
考虑看看 DBMS_STATS
包。
选项 A:使用 DBMS_STATS
程序手动设置 table、列和索引统计信息(即 SET_TABLE_STATS
、SET_COLUMN_STATS
和 SET_INDEX_STATS
, 分别)。然后使用 DBMS_STATS.LOCK_TABLE_STATS
来防止您手动设置的统计信息被覆盖(例如,当您的 table 恰好为空时,由 DBA 收集模式统计信息)。
选项 B:运行 您按原样进行操作,然后在 table 上手动收集统计信息。然后,如上,使用DBMS_STATS.LOCK_TABLE_STATS
来防止它们被覆盖。
无论哪种方式,我们的想法是设置或收集关于您的 table 的统计信息,然后将它们锁定到位。
如果您想变得更漂亮,也许您可以将其自动化。例如,
- 在安装时,手动设置统计信息并为您的第一次锁定它们 运行
- 在您的程序代码中,在 结尾 ,解锁统计数据,收集它们,然后 re-lock 它们。
我面临的一个问题(Oracle 11g):
我创建了一个 table,我们称之为 table_xyz,带有索引(不是唯一的,没有主键)。 我创建了一个程序包,假设每月插入 1000 万条记录——这不是简单的“插入”,它是数千行程序代码,其中一些实际上还从 table_xyz 中选择数据来计算要插入的数据进一步。
例如在过程中某处有这个查询
现在,有一个问题: 当程序是第一次 运行 时,table_xyz 上的所有查询都将根据 table_xyz 中有 0 条记录的时刻执行计划。 因此,所有查询都将有效地全面扫描 table_xyz,而不是在某个时候开始使用索引。 这会导致糟糕的性能,实际上在我的情况下,第一个 运行 永远不会完成...
现在,我想到了三种方法:
- 在事务中的某个时刻,重新计算统计信息。例如运行“analyze table / analyze index compute statistics”在table_xyz的记录数达到10次方后。 然而,这是不可能的,因为 ANALYZE 提交事务,我不能允许
- 在事务中的某个时刻,如上所述重新计算统计信息,但在自主事务中进行。然而,这不起作用,因为新的统计信息对于主事务不可见(我测试过)。
- 只需提示所有使用table_xyz的游标和USE_INDEX。这样就完成了工作,但是很丑陋并且在代码库中通常不受欢迎。
还有其他方法吗?
我附上一些代码。这只是一个例子,请不要尝试通过删除程序等方式优化它。
create table table_xyz (idx number(10) /*+ Specifically this is NOT a primary key */
,some_value varchar2(10)
);
create index table_xyz_idx on table_xyz (idx);
declare
cursor idxes is
select level idx
from dual d
connect by level < 100000;
current_val varchar2(10);
function calculate_some_value(p_idx number) return varchar2
is
cursor c_previous is
select t.some_value
from table_xyz t
where t.idx in (round(p_idx / 2, 0), round(p_idx / 3, 0), round(p_idx / 5, 0))
order by t.idx desc
;
x varchar2(100);
begin
open c_previous;
fetch c_previous into x;
close c_previous;
x := nvl(x, 'XYZ');
if mod(p_idx, 2) = 0 then
x := x || '2';
elsif mod(p_idx, 3) = 0 then
x := '3' || x;
elsif mod(p_idx, 5) = 0 then
x := substr(x, 1,1) || '5' || substr(x, 2, 2 + mod(p_idx, 7));
end if;
x := substr(x, 1, 10);
return x;
end calculate_some_value;
begin
for idx in idxes
loop
current_val := calculate_some_value(idx.idx);
insert into table_xyz(idx, some_value) values (idx.idx, current_val);
end loop;
end;
考虑看看 DBMS_STATS
包。
选项 A:使用 DBMS_STATS
程序手动设置 table、列和索引统计信息(即 SET_TABLE_STATS
、SET_COLUMN_STATS
和 SET_INDEX_STATS
, 分别)。然后使用 DBMS_STATS.LOCK_TABLE_STATS
来防止您手动设置的统计信息被覆盖(例如,当您的 table 恰好为空时,由 DBA 收集模式统计信息)。
选项 B:运行 您按原样进行操作,然后在 table 上手动收集统计信息。然后,如上,使用DBMS_STATS.LOCK_TABLE_STATS
来防止它们被覆盖。
无论哪种方式,我们的想法是设置或收集关于您的 table 的统计信息,然后将它们锁定到位。
如果您想变得更漂亮,也许您可以将其自动化。例如,
- 在安装时,手动设置统计信息并为您的第一次锁定它们 运行
- 在您的程序代码中,在 结尾 ,解锁统计数据,收集它们,然后 re-lock 它们。