【问题标题】:What is the fastest way to perform a date query in Oracle SQL?在 Oracle SQL 中执行日期查询的最快方法是什么?
【发布时间】:2018-01-07 07:57:06
【问题描述】:

我们有一个 6B 行表,在检索数据时给我们带来了挑战。

我们的查询在执行操作时会立即返回值...

SELECT * WHERE Event_Code = 102225120

这种即时结果正是我们所需要的。我们现在想要过滤以接收特定年份的值 - 但我们添加的那一刻......

AND EXTRACT(YEAR FROM PERFORMED_DATE_TIME) = 2017

...查询需要 10 多分钟才能开始返回任何值。

Another SO post 提到在提取多行而不是单个行时,索引不一定有助于日期查询。还有其他方法,例如使用 TRUNC 或 BETWEEN,或以 YYYY-MM-DD 格式指定日期时间进行比较。

值得注意的是,我们没有向数据库添加索引的选项,因为它是供应商的数据库。

如何添加日期过滤查询并让 Oracle 开始以最快的方式返回结果?

【问题讨论】:

  • we do not have the option to add indexes - 请您的 DBA 创建索引。如果 DBA 不愿意帮忙,那就问问你的老板。告诉他们你不需要索引,因为查询非常慢,而且它不必要地占用数据库资源,使整个系统变慢。
  • @krokodilko - 重点是数据库是“是供应商的数据库”:令人遗憾的是,未经授权的数据库架构更改会使供应商的支持合同失效,这是一个令人遗憾的常见情况.也就是说,出于许可原因,他们“没有选择权”
  • 60亿条记录是很多记录:这张表是分区的吗?

标签: sql oracle query-optimization


【解决方案1】:

另一篇 SO 帖子提到,在提取多行而不是单个行时,索引不一定有助于日期查询

这个问题和你的完全不同。首先,您上面的陈述适用于任何数据类型,而不仅仅是日期。 ma​​ny 这个词也与表中的记录数有关。如果优化器决定查询将返回表中所有记录中的许多,那么它可能会决定全表扫描比使用索引更快。在您的情况下,这意味着表中的所有记录中有多少条记录在 2017 年?此计算为您提供查询的基数,然后让您了解索引是否会更快。

现在,如果您决定索引会更快,那么根据上述情况,下一步就是了解如何构建索引。为了让优化器使用索引,它必须匹配您正在使用的条件。您不是在比较查询中的日期,而是在比较年份部分。因此,此查询不会使用日期列上的索引。您需要在年份部分创建索引,因此使用相同的条件创建索引。

我们没有向数据库添加索引的选项,因为它是供应商的数据库。

如果您无法修改数据库,则无法优化您的查询。您需要与供应商交谈并获得修改数据库的权限或要求他们为您添加索引。

【讨论】:

  • 那么您所说的是否意味着没有任何类型的日期查询比任何其他类型都快?例如,EXTRACT 的性能与 BETWEEN 和 TRUNC 以及任何其他变体一样吗?
  • 不,我没有这么说。您在哪里看到我比较答案中的不同功能?当然,它们之间是有区别的。但是它们都不会在没有索引的大表上为您提供良好的性能。如果您正确使用它们,与拥有或没有适当的索引相比,它们之间的差异可以忽略不计。但是,创建适当的索引在它们之间存在显着差异,这就是差异所在。例如,BETWEEN 的适当索引确实很棘手,但对于您的年份查询却很容易。
  • 是的 - 我的问题意味着暗示速度差异显着更快而不是微不足道的差异 - (这也符合原始问题的愿望)
  • 您能否发布两个查询的执行计划(或者最好是 SQL 监控报告)。这样你就有更好的机会得到真正的诊断而不是猜测。
【解决方案2】:

函数也可能导致所涉及的记录数量变慢。不确定基于函数的索引是否可以帮助您,但您可以尝试。

您是否尝试在表格中添加年份列?如果没有,请尝试添加年份列并使用下面的代码对其进行更新。

UPDATE table
   SET year = EXTRACT(YEAR FROM PERFORMED_DATE_TIME);

不过这需要时间。

但在此之后,您可以运行下面的查询。

 SELECT * 
   FROM table 
  WHERE Event_Code = 102225120 AND year = 2017;

此外,请尝试考虑为这种大数据使用表分区。对于初学者,请参阅下面的链接,

链接:https://oracle-base.com/articles/8i/partitioned-tables-and-indexes

【讨论】:

  • 是的,一个函数当然需要一些执行时间,但是用这样一个简单的函数来提取年份,性能差异可以忽略不计。将年份提取到单独的列仍然需要在新列上建立索引,否则查询性能将不足以保证破坏表的正常性。只有当存在有意义的差异时,我们才打破表格的正态性,通常涉及比提取年份复杂得多的计算,或者当列有大量命中时。
【解决方案3】:

恕我直言,您的问题有点模棱两可:

但是我们添加的那一刻... 和摘录(从 PERFORMED_DATE_TIME 开始的年份)= 2017 ...查询需要 10 多分钟才能开始返回任何值。

你的意思是

SELECT * WHERE Event_Code = 102225120

很快,但是

SELECT * WHERE Event_Code = 102225120 AND EXTRACT(YEAR FROM PERFORMED_DATE_TIME) = 2017

很慢???

首先,我同意 Mitch Wheat 的观点,即您应该在 2017 年 1 月 1 日至 2017 年 12 月 31 日之间尝试使用 PERFORMED_DATE_TIME,而不是 Year(field) = 2017。即使您在领域,后者几乎无法使用它,而第一种方法将受益匪浅。

我还希望您希望更具体,而不仅仅是“给我 2017 年的全部时间”,因为返回超过 1B 的行永远不会很快。

接下来,如果您无法对数据库进行更改,您能否在另一个数据库中维护“影子”?这将要求您在另一个数据库中创建一个包含所有日期值和原始表的 PK 的表,并查询这些表以找到相关的 PK 值,然后将它们加入到您的原始表中以查找您需要的任何内容。最大的问题是您需要使阴影与原始表保持同步。如果您知道原始表只在一夜之间更改,您可以在早上合并更改并查询一整天。如果应用程序是“实时(ish)”,那么如果没有一些聪明的想法,这可能无法工作......是的,你的 6B 值的初始负载将相当重 =)

【讨论】:

  • 即使您在该字段上有一个索引,后者也几乎无法使用它,而第一种方法将受益匪浅,您假设日期字段上已存在索引。如果这是真的并且我们无法修改索引或添加新索引,那么您的声明是有效的。否则,在年份上创建索引将使按年份进行搜索的速度与使用带有字段索引的BETWEEN 进行搜索一样快。同样在大多数情况下,时间很重要,您需要将其包含在 BETWEEN 范围内,如果最后一个小数秒内有记录,则使用起来会更加困难
  • @RacilHilan 是的,我忘记了在 Oracle 中您可以直接在 EXTRACT(YEAR FROM PERFORMED_DATE_TIME) 上放置索引。习惯于 MSSQL 需要添加计算字段并将其索引以 实现 它,这可能会被认为比简单地在 YEAR FROM PERFORMED_DATE_TIME 上添加索引更具侵入性。我主要是想指出WHERE f(x) = @value 通常不会从x 上的索引中受益很多,但是将查询转换为WHERE x = f(@value) 会。
  • 是的,f(x) 根本不使用x 上的索引,因此与没有索引相同。顺便说一句,在 SQL Server 中,确实必须在计算域上创建索引,但是如果不使用PERSISTED 标记计算域,则它不会存储在表中。这几乎就像 Oracle 和 MySQL。唯一的问题是您需要避免在所有查询中使用计算字段以避免性能下降。所以我同意你的观点,这比 Oracle 和 MySQL 方式更具侵入性。
【解决方案4】:

这可能有用(因为您避免使用函数(导致上下文切换的原因),并且如果您的日期字段上有索引,则可以使用它):

with 
dt as
(
select
        to_date('01/01/2017', 'DD/MM/YYYY') as d1,    
        to_date('31/01/2017', 'DD/MM/YYYY') as d2    
       from dual
),  
   dates as
(
select 
        dt.d1 + rownum -1 as d
from dt
connect by dt.d1 + rownum -1 <= dt.d2
)
select *
from your_table, dates
where  dates.d = PERFORMED_DATE_TIME

【讨论】:

    【解决方案5】:

    将日期文字移动到 RHS:

    AND PERFORMED_DATE_TIME >= date '2017-01-01' 
    AND PERFORMED_DATE_TIME < date '2018-01-01'
    

    但如果PERFORMED_DATE_TIME 上没有(未公开的)适当索引,查询不太可能更快。

    在第三方数据库中创建索引的一个选项是在索引中编写脚本,然后在任何供应商升级之前运行脚本以删除您添加的任何索引。如果索引很重要,请让供应商将其添加到他们的数据库设计中。

    【讨论】:

    • 其实就是一个BETWEEN,不是吗?
    • 是的,但是我最近在阅读了 Aaron Bertrand 的文章后开始使用等效语法(虽然那是针对 sql server)...Between 是隐含的,但上面的语法是显式的。
    • 对;人们应该知道 BETWEEN 做了什么。使用您的示例中的语法,很明显。
    • DATE '2017-12-31'2017-12-31T00:00:00,因此这将排除介于 2017-12-31T00:00:012017-12-31T23:59:59 之间的任何值。使用AND PERFORMED_DATE_TIME &gt;= DATE '2017-01-01' AND PERFORMED_DATE_TIME &lt; DATE '2018-01-01' 会更好。
    • 更新:我知道时间部分有什么东西在唠叨我!谢谢
    猜你喜欢
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2019-10-28
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2015-05-08
    相关资源
    最近更新 更多