如何在Oracle中调整范围/间隔查询?

时间:2014-03-06 14:10:21

标签: sql oracle oracle11g

我的表格A的时间间隔为(COL1, COL2)

CREATE TABLE A (
  COL1 NUMBER(15) NOT NULL,
  COL2 NUMBER(15) NOT NULL,
  VAL1 ...,
  VAL2 ...
);
ALTER TABLE A ADD CONSTRAINT COL1_BEFORE_COL2 CHECK (COL1 <= COL2);

间隔保证是“独占的”,即它们永远不会重叠。换句话说,此查询不会产生任何行:

SELECT *
FROM (
  SELECT
    LEAD(COL1, 1) OVER (ORDER BY COL1) NEXT,
    COL2
  FROM A
)
WHERE COL2 >= NEXT;

(COL1, COL2)目前有一个索引。现在,我的查询如下:

SELECT /*+FIRST_ROWS(1)*/ *
FROM A
WHERE :some_value BETWEEN COL1 AND COL2
AND ROWNUM = 1

对于A的低值,这对于:some_value中的数百万条记录表现良好(不到一毫秒),因为它们对索引非常有选择性。但由于访问谓词的选择性较低,它对:some_value的高值执行得非常糟糕(差不多一秒)。

执行计划对我来说似乎很好。由于现有索引已完全涵盖谓词,因此我得到了预期的INDEX RANGE SCAN

------------------------------------------------------
| Id  | Operation                    | Name | E-Rows |
------------------------------------------------------
|   0 | SELECT STATEMENT             |      |        |
|*  1 |  COUNT STOPKEY               |      |        |
|   2 |   TABLE ACCESS BY INDEX ROWID| A    |      1 |
|*  3 |    INDEX RANGE SCAN          | A_PK |        |
------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM=1)
   3 - access("VAL2">=:some_value AND "VAL1"<=:some_value)
       filter("VAL2">=:some_value)

3中,很明显,访问谓词只对:some_value的低值有选择性;而对于较高的值,过滤操作会在索引上“开始”。

是否有任何方法可以将此查询提升为快速,而不管:some_value的值是多少?如果需要进一步规范化,我可以完全重新设计表格。

4 个答案:

答案 0 :(得分:10)

你的尝试很好,但错过了一些关键问题。

让我们慢慢开始。我假设COL1上有一个索引,我实际上并不介意COL2是否包含在那里。

由于您对数据的限制(特别是非重叠),您实际上只需要之前的行COL1 <=某个值。 .. [ - 休息一下 - ]你按COL1

命令

这是classic Top-N query

select *
  FROM ( select *
          from A
         where col1 <= :some_value
         order by col1 desc
       )
 where rownum <= 1;

请注意,必须使用ORDER BY才能获得明确的排序顺序。在WHERE之后应用ORDER BY时,您现在必须将top-n过滤器包装在外部查询中。

这几乎已经完成,我们实际上需要过滤COL2的唯一原因是过滤掉根本不属于该范围的记录。例如。如果some_value为5且您有这些数据:

  COL1 | COL2
     1 |  2
     3 |  4   <-- you get this row 
     6 | 10

如果COL2为5,则此行将是正确的,但不幸的是,在这种情况下,查询的正确结果为[空集]。这是我们需要像这样过滤COL2的唯一原因:

select *
  FROM ( select *
           FROM ( select *
                    from A
                   where col1 <= :some_value
                   order by col1 desc
                )
          where rownum <= 1
        )
  WHERE col2 >= :some_value;

你的方法有几个问题:

  • 缺少ORDER BY - 与rownum过滤器相关的危险!
  • 过早应用Top-N子句(rownum过滤器)。如果没有结果怎么办?数据库读取索引直到结束,rownum(STOPKEY)永远不会开始。
  • 优化器故障。使用between谓词,我的11g安装没有按降序读取索引的想法,所以它实际上是从开头(0)向上读取它直到找到COL2值匹配 - 或 - COL1超出范围。

COL1 | COL2
   1 |  2   ^
   3 |  4   |      (2) go up until first match.
            +----- your intention was to start here
   6 | 10

实际发生的事情是:

  COL1 | COL2
     1 |  2   +----- start at the beginning of the index
     3 |  4   |      Go down until first match.      
              V
     6 | 10

查看我的查询的执行计划:

------------------------------------------------------------------------------------------
| Id  | Operation                       | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |        |     1 |    26 |     4   (0)| 00:00:01 |
|*  1 |  VIEW                           |        |     1 |    26 |     4   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY                 |        |       |       |            |          |
|   3 |    VIEW                         |        |     2 |    52 |     4   (0)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID | A      | 50000 |   585K|     4   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN DESCENDING| SIMPLE |     2 |       |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

请注意INDEX RANGE SCAN **DESCENDING**

最后,为什么我没有在索引中包含COL2?这是一个单行top-n查询。您最多可以保存一个表访问权限(不管上面的Rows估计是什么意思!)如果您希望在大多数情况下找到一行,那么无论如何你都需要去其他列(可能),所以你不会保存任何东西,只是消耗空间。如果您的查询根本没有返回任何内容,那么包含COL2只会提高性能!

相关:

答案 1 :(得分:3)

我认为,因为范围不相交,所以可以将col1定义为主键并执行如下查询:

SELECT *
  FROM    a
       JOIN
          (SELECT MAX (col1) AS col1
             FROM a
            WHERE col1 <= :somevalue) b
       ON a.col1 = b.col1;

如果您必须添加的范围之间存在差距:

Where col2 >= :somevalue

作为最后一行。

执行计划:

SELECT STATEMENT  
 NESTED LOOPS  
  VIEW  
   SORT AGGREGATE 
    FIRST ROW  
     INDEX RANGE SCAN (MIN/MAX) PKU1
  TABLE ACCESS BY INDEX A
   INDEX UNIQUE SCAN PKU1

答案 2 :(得分:0)

将堆表更改为IOT表可能会提供更好的性能。

答案 3 :(得分:-1)

我没有生成样本数据来测试这个,但你可能想尝试一下。

ALTER TABLE A ADD COL3 NUMBER(15);

UPDATE A SET COL3 = COL2 - COL1;

在COL3上创建索引。

SELECT /*+FIRST_ROWS(1)*/ *
FROM A
WHERE :some_value < COL3
AND ROWNUM = 1;