在timestamp列上为使用year函数的查询创建索引

时间:2014-06-20 20:53:39

标签: sql db2 query-optimization query-tuning

我有一张包含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)列上添加索引会改善性能,考虑在列上使用年份函数吗?

5 个答案:

答案 0 :(得分:4)

索引不会真正有用,因为您已经形成了查询,因此它必须执行完整的表扫描,索引或无索引。您必须形成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子句中删除它。

这称为sargability,你可以谷歌上各种好的信息。

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

答案 1 :(得分:1)

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

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

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

即使使用大表,也可以测试索引是否有所不同。您只能将部分行复制到新表中,并将新表上的执行计划与索引进行比较,例如:

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

答案 2 :(得分:1)

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

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

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

...索引超过[dayOfMonthmonthyearisoDate],您的查询可以像这样重写:

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数据)平分,并且范围的结束也是(是的,在列上进行数学运算通常会使数学使用的数据方面的索引不合格。如果优化器非常聪明,它将生成一个包含范围开始/结束的虚拟中间表。

因此,查询的基于索引(以及可能的仅索引)访问。学会喜欢索引维度表,可以用于范围查询(日历表是最有用的之一)。

答案 3 :(得分:0)

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

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

我建议仅使用“created”列作为索引的键,并且不要在History表中包含任何其他列,因为它们将不被使用,只会产生比所需更多的读取。

当然,在对具有大量INSERT,UPDATE,DELETE活动的表创建索引时要小心,因为新索引将使这些操作在表上执行时更加昂贵。

答案 4 :(得分:0)

如前所述,在您的情况下,将不会使用索引,因为索引是在“已创建”列上创建的,并且您正在查询“年份(已创建)”。

您可以做的是将两个生成的列year_gen = year(create)和month_gen = MONTHNAME(created)添加到您的表中并为这两列编制索引。 DB2 Query Optimizer将自动使用这两个生成的列,它还将使用在这些列上创建的索引。

代码应该是类似的(但不是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将表的完整性设置为“由用户检查”。