【问题标题】:Poor performance on Oracle where clauseOracle where 子句的性能不佳
【发布时间】:2013-03-26 22:44:36
【问题描述】:

我有以下 Oracle 表:

create table my_table(
   start int,
   end int
);

insert into my_table values(1, 3);
insert into my_table values(5, 7);
insert into my_table values(11, 200);
insert into my_table values(311, 5000);
insert into my_table values(60004, 60024);
insert into my_table values(123213, 12312312);

这个表有1M行并存储数字范围('start','end'),所有数字都是唯一的,它没有重复范围并且任何数字只能在这个表中的一个范围内,我有以下查询它传递一个变量 my_number 来标识范围的“开始”。

 execute immediate 
    'select start from my_table where :1 between start and end' using my_number

我已经在这两个字段上建立了组合索引。问题是当my_number 小时,查询性能很好,但是当my_number 增加时,查询时间不断增加。如果 my_number 大得多,则需要相当长的时间才能完成。有人有办法改进这个查询吗?方法可以包括重新设计 my_table。谢谢。

【问题讨论】:

  • 你在开始和结束时有一个由两部分组成的复合键/索引吗?
  • 我们可以看看explain plan的输出吗?
  • 是的,开始和结束的两部分复合索引。解释很简单,索引扫描,start

标签: sql database performance oracle oracle11g


【解决方案1】:

如果您将架构更改为:

create table my_table(
   start int,
   range_size int
);

insert into my_table values(1, 2);
insert into my_table values(5, 2);
insert into my_table values(11, 189);
insert into my_table values(311, 4689);
insert into my_table values(60004, 20);
insert into my_table values(123213, 12300001);

那么您只能在start 列上建立索引。

execute immediate 
    'select start from (select start, range_size from my_table where start < :1 order by start asc limit 1) tmp where :1 < start+range_size' using my_number

这可能会提高一些性能。

【讨论】:

  • 作为一个额外的好处,这个解决方案将索引大小减半,因此更有可能完全适合 RAM。
  • 'start :1' 吗?因为 my_number 应该从头到尾定位?订单声明会为此产生额外费用吗?如果是,我可以将表中的行顺序放到 ASC 中吗?所以我在执行非常查询时不订购它。
  • 'start &lt; :1' 是正确的。想想不等式:start &lt; my_number &lt; end.
  • 哦,对了。 Oracle 没有 LIMIT 运算符。什么鬼
【解决方案2】:

你做完了吗?

create table my_table(
start int,
end int
constraint PK_comp primary key (start, end)
) ;

【讨论】:

  • 是的,我已经尝试过了,即使我添加了 PK,它的性能确实比我在两个字段上仅添加一个组合索引的效果差
【解决方案3】:

我认为您应该创建 2 个索引,一个在开始列,一个在结束列。 然后不要选择 between 选项,而是选择大于然后开始和小于然后结束的位置。 然后,您将为每个 where 子句使用一个索引。

我希望这有助于提高性能。

【讨论】:

    【解决方案4】:

    这是一个试图欺骗甲骨文使其表现得像竞争对手一样的案例,我只是猜测无法访问甲骨文。也许自我加入可以做到这一点?每列都有索引单独

    SELECT t1.start
    FROM my_table t1 JOIN my_table t2
    ON t1.start=t2.start AND t2."end"=t1."end"
    AND t1.start <= :1
    AND t2.end >= :1
    

    这看起来很傻,但直接的解决方案是 Joe Frambach 的。它欺骗了我确实拥有的 Postgres,只进行索引搜索。

    顺便说一句,Postgres 对 end 作为列名非常不满。我希望你的真实表不要在那里使用保留字。

    【讨论】:

      【解决方案5】:

      为每一列创建一个索引并使用这个查询:

      select start_num
      from my_table
      where
          start_num =
          (
              --Last start <= number
              select start_num
              from
              (
                  select start_num
                  from my_table
                  where :1 >= start_num
                  order by start_num desc
              )
              where rownum = 1
          ) and
          end_num =
          (
              --First end >= number
              select end_num
              from
              (
                  select end_num
                  from my_table
                  where :1 <= end_num
                  order by end_num
              )
              where rownum = 1
          );
      

      哎呀。可能有更好的方法来写这个。或者您可能希望将其包装在一个函数中。

      问题

      测试数据(带有非保留字列名):

      drop table my_table;
      create table my_table(
         start_num int,
         end_num int
      );
      insert into my_table select level*2,level*2+1 from dual connect by level <= 1000000;
      commit;
      create index my_table_index on my_table(start_num, end_num);
      begin
          dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
      end;
      /
      

      低数字几乎是瞬时的 - 0.015 秒

      select start_num from my_table where 2 between start_num and end_num;
      

      数字越大越慢 - 0.125 秒

      select start_num from my_table where 1000000 between start_num and end_num;
      

      范围扫描和全表扫描之间只有一个点。

      explain plan for select start_num from my_table where 402741 between start_num and end_num;
      select * from table(dbms_xplan.display);
      
      Plan hash value: 3804444429
      
      ------------------------------------------------------------------------------
      | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
      ------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT  |          |   160K|  1570K|   622   (2)| 00:00:08 |
      |*  1 |  TABLE ACCESS FULL| MY_TABLE |   160K|  1570K|   622   (2)| 00:00:08 |
      ------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
      
      
      explain plan for select start_num from my_table where 402742 between start_num and end_num;
      select * from table(dbms_xplan.display);
      
      
      Plan hash value: 3804444429
      
      ------------------------------------------------------------------------------
      | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
      ------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT  |          |   160K|  1570K|   622   (2)| 00:00:08 |
      |*  1 |  TABLE ACCESS FULL| MY_TABLE |   160K|  1570K|   622   (2)| 00:00:08 |
      ------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
      

      但问题不在于 Oracle 不使用索引。以幼稚的方式使用索引并没有帮助。事实上,这甚至更慢,为 0.172 秒:

      select /*+ index(my_table my_table_index) */ start_num
      from my_table
      where 1000000 between start_num and end_num;
      

      解决方案

      创建新索引:

      drop index my_table_index;
      create index my_table_index1 on my_table(start_num);
      create index my_table_index2 on my_table(end_num);
      begin
          dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
      end;
      /
      

      结果是即时的,对于任何数字:

      select start_num
      from my_table
      where
          start_num =
          (
              --Last start <= number
              select start_num
              from
              (
                  select start_num
                  from my_table
                  where 1000000 >= start_num
                  order by start_num desc
              )
              where rownum = 1
          ) and
          end_num =
          (
              --First end >= number
              select end_num
              from
              (
                  select end_num
                  from my_table
                  where 1000000 <= end_num
                  order by end_num
              )
              where rownum = 1
          );
      

      该计划看起来很棒 - 这可能是您可以获得的最佳性能。

      Plan hash value: 522166032
      
      --------------------------------------------------------------------------------------------------
      | Id  | Operation                      | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
      --------------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT               |                 |     1 |    10 |    10   (0)| 00:00:01 |
      |*  1 |  TABLE ACCESS BY INDEX ROWID   | MY_TABLE        |     1 |    10 |     4   (0)| 00:00:01 |
      |*  2 |   INDEX RANGE SCAN             | MY_TABLE_INDEX2 |     1 |       |     3   (0)| 00:00:01 |
      |*  3 |    COUNT STOPKEY               |                 |       |       |            |          |
      |   4 |     VIEW                       |                 |     3 |    39 |     3   (0)| 00:00:01 |
      |*  5 |      INDEX RANGE SCAN          | MY_TABLE_INDEX2 |     3 |    18 |     3   (0)| 00:00:01 |
      |*  6 |   COUNT STOPKEY                |                 |       |       |            |          |
      |   7 |    VIEW                        |                 |     2 |    26 |     3   (0)| 00:00:01 |
      |*  8 |     INDEX RANGE SCAN DESCENDING| MY_TABLE_INDEX1 |   500K|  2929K|     3   (0)| 00:00:01 |
      --------------------------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         1 - filter("START_NUM"= (SELECT "START_NUM" FROM  (SELECT "START_NUM" "START_NUM" FROM 
                    "MY_TABLE" "MY_TABLE" WHERE "START_NUM"<=1000000 ORDER BY "START_NUM" DESC) 
                    "from$_subquery$_002" WHERE ROWNUM=1))
         2 - access("END_NUM"= (SELECT "END_NUM" FROM  (SELECT "END_NUM" "END_NUM" FROM 
                    "MY_TABLE" "MY_TABLE" WHERE "END_NUM">=1000000 ORDER BY "END_NUM") "from$_subquery$_004" 
                    WHERE ROWNUM=1))
         3 - filter(ROWNUM=1)
         5 - access("END_NUM">=1000000)
         6 - filter(ROWNUM=1)
         8 - access("START_NUM"<=1000000)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        • 2011-04-17
        • 1970-01-01
        • 2014-06-11
        • 2017-02-27
        • 2017-05-27
        • 2012-07-02
        相关资源
        最近更新 更多