我的表格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
的值是多少?如果需要进一步规范化,我可以完全重新设计表格。
答案 0 :(得分:10)
你的尝试很好,但错过了一些关键问题。
让我们慢慢开始。我假设COL1
上有一个索引,我实际上并不介意COL2
是否包含在那里。
由于您对数据的限制(特别是非重叠),您实际上只需要行之前的行COL1
<=
某个值。 .. [ - 休息一下 - ]你按COL1
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
过滤器相关的危险!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;