我们的数据库中有很多表,其中的数据仅在特定时间段内相关/有效。例如,契约,它们有一个start_date和一个end_date。并且它不一定是整整几个月。
现在这是针对此表的典型查询类型:
SELECT
*
FROM
contracts c
WHERE
c.start_date <= :1
AND c.end_date >= :2
AND c.region_id = :3
由于我们在表中有20年的数据(~7000天),因此日期是非常好的过滤条件,尤其是:1和:2是同一天。 region_id不是一个很好的过滤标准,因为没有那么多(~50)。在这个例子中,我们在桌子上有(其中包括)2个索引:
contracts_valid_index (start_date, end_date)
contracts_region (region_id)
不幸的是,上面的查询通常会使用contracts_region索引,因为优化器认为它更便宜。这背后的原因很简单:当我在数据中间选择一天时,数据库会认为start_date上的索引不会真正好,因为它只会过滤掉一半的数据。通过查看end_date,同样适用。所以优化器认为他只能过滤掉1/4的数据。因为他不知道start_date和end_date通常非常接近,所以这个索引会非常有选择性。
使用contracts_valid_index的执行计划的成本高于使用contracts_region的执行计划。但实际上contract_valid_index要好得多。
我目前不认为我可以通过制作更好的索引来加快查询速度(除了删除除了contract_valid_index以外的所有索引)。但也许我的数据模型对查询优化器来说不是很好。所以我假设其他人也有类似的需求,并且很想知道他们如何建模他们的数据或优化他们的数据表/索引。
有什么建议吗?
答案 0 :(得分:1)
由于您表明您使用的是Oracle 12c,因此将Start_Date和End_Date列定义为temporal有效时间列可能会有所帮助,前提是它们与适当的时间有效性语义匹配(start_date和end_date需要是时间戳,end_date必须是&gt; start_date或者可能为null且有效时间段包括开始日期但排除结束日期,即它是部分关闭/开放范围,而不像通常的运算符之间表示完全关闭范围)。例如:
ALTER TABLE contracts ADD (PERIOD FOR valid_time (start_date, end_date));
然后,您可以在合同表中查询给定的有效期:
SELECT
c.*
FROM
contracts VERSIONS PERIOD FOR valid_time BETWEEN :1 AND :2 c
WHERE
c.region_id = :3
这在语义上类似于:
SELECT
c.*
FROM
contracts c
WHERE
:1 < end_date
AND start_date <= :2
AND c.region_id = :3
或者查询对特定时间点而不是一段时间有效的记录:
SELECT
c.*
FROM
contracts AS OF PERIOD FOR valid_time :1 c
WHERE
c.region_id = :2
在语义上类似于:
SELECT
c.*
FROM
contracts c
WHERE
:1 BETWEEN start_date AND end_date
and :1 <> end_date
and c.region_id = :2
我不确定start_date和end_date的空值是否分别表示时间的开始和结束,因为我目前没有要测试的R12实例。
答案 1 :(得分:1)
我之前遇到过与MySQL数据库中大量IP地址相关的索引使用问题(请耐心等待;实际上是同样的问题)。
The solution我发现(通过大量的谷歌搜索,我没有因为发明它而得到赞誉)是使用地理空间索引。这专门用于查找范围内的数据。大多数实现(包括在mysql中的实现)都硬连接到二维空间,而ip地址和时间是一维的,但是将一维坐标映射到二维空间是微不足道的(请参阅链接以获得一步一步的解释)
抱歉,我对甲骨文的地理空间功能一无所知,因此我无法提供任何示例代码,但它确实支持地理空间索引,因此可以有效地解决您的查询。
答案 2 :(得分:0)
您可以尝试以下查询以查看它是否更有效:
WITH t1 AS (
SELECT *
FROM contracts c
WHERE c.start_date <= :1
AND c.end_date >= :2
)
SELECT *
FROM t1
WHERE c.region_id = :3
虽然它可能会阻止使用contract_region索引的任何可能性。
或者,您可以尝试提示查询以使用所需的索引:
SELECT /*+ INDEX(c contracts_valid_index) */
*
FROM
contracts c
WHERE
c.start_date <= :1
AND c.end_date >= :2
AND c.region_id = :3
或者暗示它不使用不受欢迎的索引:
SELECT /*+ NO_INDEX(c contracts_region ) */
*
FROM
contracts c
WHERE
c.start_date <= :1
AND c.end_date >= :2
AND c.region_id = :3
在不使用提示的情况下自行测试时,我发现在选择可用日期范围的开头或结尾附近的日期时,优化器正在使用INDEX_RS_ASC提示。如下所示将其添加到查询中会导致我的测试使用所需的索引,即使日期范围更接近日期范围的中心:
SELECT /*+ INDEX_RS_ASC(c contracts_valid_index) */
*
FROM
contracts c
WHERE
c.start_date <= :1
AND c.end_date >= :2
AND c.region_id = :3
我的样本数据包括在50个地区均匀分布的10,000,000行,每行1000天,有效期为30天。