交易中快速变化的统计数据 - 固定执行计划

Statistics rapidly changing within transaction - fixing execution plan

我面临的一个问题(Oracle 11g):

我创建了一个 table,我们称之为 table_xyz,带有索引(不是唯一的,没有主键)。 我创建了一个程序包,假设每月插入 1000 万条记录——这不是简单的“插入”,它是数千行程序代码,其中一些实际上还从 table_xyz 中选择数据来计算要插入的数据进一步。

例如在过程中某处有这个查询

现在,有一个问题: 当程序是第一次 运行 时,table_xyz 上的所有查询都将根据 table_xyz 中有 0 条记录的时刻执行计划。 因此,所有查询都将有效地全面扫描 table_xyz,而不是在某个时候开始使用索引。 这会导致糟糕的性能,实际上在我的情况下,第一个 运行 永远不会完成...

现在,我想到了三种方法:

  1. 在事务中的某个时刻,重新计算统计信息。例如运行“analyze table / analyze index compute statistics”在table_xyz的记录数达到10次方后。 然而,这是不可能的,因为 ANALYZE 提交事务,我不能允许
  2. 在事务中的某个时刻,如上所述重新计算统计信息,但在自主事务中进行。然而,这不起作用,因为新的统计信息对于主事务不可见(我测试过)。
  3. 只需提示所有使用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_STATSSET_COLUMN_STATSSET_INDEX_STATS, 分别)。然后使用 DBMS_STATS.LOCK_TABLE_STATS 来防止您手动设置的统计信息被覆盖(例如,当您的 table 恰好为空时,由 DBA 收集模式统计信息)。

选项 B:运行 您按原样进行操作,然后在 table 上手动收集统计信息。然后,如上,使用DBMS_STATS.LOCK_TABLE_STATS来防止它们被覆盖。

无论哪种方式,我们的想法是设置或收集关于您的 table 的统计信息,然后将它们锁定到位。

如果您想变得更漂亮,也许您可​​以将其自动化。例如,

  1. 在安装时,手动设置统计信息并为您的第一次锁定它们 运行
  2. 在您的程序代码中,在 结尾 ,解锁统计数据,收集它们,然后 re-lock 它们。