在时间戳列上为使用年份函数的查询创建索引

Posted

技术标签:

【中文标题】在时间戳列上为使用年份函数的查询创建索引【英文标题】:Creating index on timestamp column for query which uses year function 【发布时间】:2014-06-20 20:53:38 【问题描述】:

我有一个包含 900 万条记录的 HISTORY 表。我需要找到按年、按月创建的记录。我使用的是查询 1,但是它超时了几次。

SELECT 
    year(created) as year, 
    MONTHNAME(created) as month, 
    count(*) as ymcount  
FROM 
    HISTORY 
GROUP BY 
    year(created), MONTHNAME(created);

我决定添加where year(created),这次查询需要 30 分钟(是的,需要很长时间)才能执行。

SELECT 
    year(created) as year, 
    MONTHNAME(created) as month, 
    count(*) as ymcount  
FROM 
    HISTORY 
WHERE 
    year(created) = 2010
GROUP BY  
    year(created), MONTHNAME(created) ;

我正计划在created 时间戳列上添加索引,但在此之前,我需要征求意见(因为索引这么大的表需要很长时间)。

考虑到在列上使用年份函数,在 created(timestamp) 列上添加索引会提高性能吗?

【问题讨论】:

你使用什么数据库系统? 它的 db2.. 我忘了标记.. 任何方式都应该是一个常见问题。 是的,但是使用正确的标签,您可能会得到比没有更好的答案。而且它比错误的sql-server 标签要好得多。 可用的 DB2 索引类型取决于平台。您指的是 DB2 for i、LUW 还是 z/OS? 【参考方案1】:

索引并没有真正的帮助,因为您已经形成了查询,因此它必须执行完整的表扫描、索引或无索引。你必须形成where 子句,所以它的形式是:

where field op constant

field 当然是你的领域; op= <= => <> between in 等,常量要么是直接常量 42,要么是可以执行一次并缓存结果的操作 getdate()

像这样:

where created >= DateFromParts( @year, 1, 1 )
  and created < DateFromParts( @year + 1, 1, 1 )

DateFromParts 函数将生成一个在查询期间保持有效的值。如果created 被索引,那么现在优化器将能够准确地寻找正确日期的开始位置,并判断该范围内的最后一个日期何时被处理并且可以停止。您可以在其他任何地方保留year(created)——只需从where 子句中删除它即可。

这叫做可搜索性,你可以用谷歌搜索各种好的信息。

附:这是 Sql Server 格式,但您应该能够在您使用的任何 DBMS 中计算“指定年份的开始”和“指定年份之后的开始”。

【讨论】:

您确定查询会执行吗?由于我使用的是计数功能,所有其他选择都应该是组子句的一部分 我无法访问 db2,但它在 Oracle 中的工作方式如下:sqlfiddle.com/#!4/1f408/1/0 如果您注意到,您唯一分组的是 /created/ 的不同表现形式,因此在 WHERE 中满足您的所有需求。【参考方案2】:

当索引有助于缩小读取的行数时,将使用索引。

当它完全避免读取表格时,它也会被使用。当索引包含查询中引用的所有列时就是这种情况。

在您的情况下,唯一引用的列是 created,因此在此列上添加索引应该有助于减少必要的读取并改善查询的整体运行时间。但是,如果created 是表中的唯一列,则索引不会在第一个查询中更改任何内容,因为它不会减少要读取的页数。

即使是大表,您也可以测试索引是否会产生影响。您可以仅将部分行复制到新表中,并比较新表上带有和不带有索引的执行计划,例如

insert into testhistory
select *
from history
fetch first 100000 rows only

【讨论】:

【参考方案3】:

您需要所谓的Calendar Table(特定示例使用 SQL Server,但该解决方案应该具有适应性)。然后,您需要很多索引(因为写入很少,这是用于分析的主要维度表)。

假设您有一个如下所示的最小日历表:

CREATE TABLE Calendar (isoDate     DATE,
                       dayOfMonth  INTEGER,
                       month       INTEGER,
                       year        INTEGER);

...如果索引超过 [dayOfMonth, month, year, isoDate],您的查询可以这样重写:

SELECT Calendar.year, Calendar.month,
       COUNT(*) AS ymCount
FROM Calendar
JOIN History
  ON History.created >= Calendar.isoDate
     AND History.created < Calendar.isoDate + 1 MONTH
WHERE Calendar.dayOfMonth = 1
GROUP BY Calendar.year, Calendar.month

WHERE Calendar.dayOfMonth = 1 自动将结果限制为每年 12 个。范围的开始通常与索引一起定位(给定 SARGable 数据),范围的结束也是如此(是的,在列上进行数学运算通常会使索引不合格......在使用数学的一侧。如果优化器非常聪明,它将生成一个包含范围开始/结束的虚拟中间表。

因此,查询的基于索引(并且可能仅索引)访问。学会爱上可用于范围查询的索引维度表(日历表是最有用的一种)。

【讨论】:

这个建议对我不起作用,我尝试了几种方法,并且得到以下错误 错误代码:-104,SQL 状态:42601] DB2 SQL 错误:SQLCODE=-104,SQLSTATE= 42601 ' SELECT calendar.yearno, calendar.monthno, COUNT(*) AS ymCount FROM calendar JOIN History ON History.created >= calendar.isoDate AND History.created 如果日历表的粒度是一天,您应该加入 ... AND History.created &lt; Calendar.isoDate + 1 DAY 而不是 ... + 1 MONTH,否则您可能会因为加入而得到笛卡尔积。 @Vinayak - -104 通常是 DB2 表示缺少某些东西的方式。你真的有日历表吗?如果你不这样做,你可以用 CTE 创建一个虚拟的。否则,错误消息应包含有关缺失/错误的更多信息。 @AndriyM 好吧,我希望谷物是 1 天。否则WHERE Calendar.dayOfMonth = 1 会有点傻。 History.created 是一个时间戳,所以我需要某种范围,因为我不知道确切的值。 啊,是的,dayOfMonth 上也有过滤器。抱歉,我忽略了这一点(愚蠢的我)。【参考方案4】:

我假设您正在使用基于标签的 SQL Server。

是的,索引将使您的查询更快。

我建议仅使用“created”列作为索引的键,并且不要包含 History 表中的任何其他列,因为它们将不会被使用并且只会导致读取次数超出必要的次数。

当然,当您在具有大量 INSERT、UPDATE、DELETE 活动的表上创建索引时请注意,因为您的新索引会使这些操作在表上执行时成本更高。

【讨论】:

看起来 OP 没有自己添加那个 SQL Server 标记。基于monthname 可能被访问? 其实可能db2来自他们之前的问题。【参考方案5】:

如前所述,在您的情况下,不会使用索引,因为索引是在“created”列上创建的,而您正在查询“year(created)”。

您可以做的是在您的表中添加两个生成的列 year_gen = year(create) 和 month_gen = MONTHNAME(created) 并对这两列进行索引。 DB2 查询优化器将自动使用这两个生成的列,它还将使用在这些列上创建的索引。

代码应该类似于(但不是 100% 确定,因为我没有要测试的 DB2)

SET INTEGRITY FOR HISTORY OFF CASCADE DEFERRED @
ALTER TABLE HISTORY ADD COLUMN YEAR_GEN SMALLINT GENERATED ALWAYS AS (YEAR(CREATE)), 
ADD COLUMN MONTH_GEN VARCHAR(20) GENERATED ALWAYS AS (YEAR(CREATE)) @
SET INTEGRITY FOR HISTORY IMMEDIATE CHECKED FORCE GENERATED @
CREATE INDEX HISTORY_YEAR_IDX ON HISTORY YEAR_GEN ASC CLUSTER @
CREATE INDEX HISTORY_MONTH_IDX ON HISTORY YEAR_GEN ASC @

只是一个旁注:set integrity off 是强制添加生成的列。在您将完整性重置为 checked 并强制重新计算生成的列之前,您的表无法访问(这可能需要一段时间)。 在没有cascade deferred 的情况下设置完整性关闭也会将每个带有HISTORY 表外键的表设置为OFF。您也必须手动重置这些表的完整性。如果我没记错的话,将cascade deferred 与传入的外键结合使用可能会导致 DB2 将表的完整性设置为“由用户检查”。

【讨论】:

感谢您的建议!我们之前没有考虑过这个选项,因为修改拥有超过 900 万条记录的表会太慢。而且所有的 DML 执行都会很慢。 如果您无法承受合理的停机时间,那么这确实不是最好的方法,因为重新检查所有完整性需要一段时间。

以上是关于在时间戳列上为使用年份函数的查询创建索引的主要内容,如果未能解决你的问题,请参考以下文章

在已经存在主键或唯一键约束的列上创建索引

在沙发上为 ARRAY_REMOVE 创建索引

在谷歌表单时间戳列上查询“今天”?

PostgreSql jsonb 列上的 GIN 索引未在查询中使用

Oracle创建索引SQL简单的例子,在表中的指定字段和如何使用索引呢?

Oracle创建索引SQL简单的例子,在表中的指定字段和如何使用索引呢?