日期范围查询的SQL索引

时间:2017-10-16 09:16:49

标签: sql database indexing sql-server-2014 database-performance

有几天,我一直在努力提高我的数据库的性能,并且有些问题我仍然对SQL Server数据库中的索引感到困惑。

我会尽量提供尽可能多的信息。

我的数据库目前包含大约10万行,并且会继续增长,因此我正试图找到一种方法让它更快地运行。

我也写信给这张桌子,所以如果你的建议会大大缩短写作时间,请告诉我。

总体目标是选择具有日期范围内特定名称的所有行。

通常会选择超过3,000行...

表架构:

CREATE TABLE [dbo].[reports]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [IsDuplicate] [bit] NOT NULL,
    [IsNotValid] [bit] NOT NULL,
    [Time] [datetime] NOT NULL,
    [ShortDate] [date] NOT NULL,
    [Source] [nvarchar](350) NULL,
    [Email] [nvarchar](350) NULL,

    CONSTRAINT [PK_dbo.reports] 
        PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]

这是我正在使用的SQL查询:

SELECT * 
FROM [db].[dbo].[reports]
WHERE Source = 'name1' 
  AND ShortDate BETWEEN '2017-10-13' AND '2017-10-15'

据我了解,我提高效率的最佳方法是在SourceShortDate上创建非聚集索引,而不会影响写入时间。

我喜欢这样,索引架构:

CREATE NONCLUSTERED INDEX [Source&Time] 
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)

现在我们遇到了让我完全迷失的棘手部分,上面的索引有时会起作用,有时候一半是有效的,有时根本不起作用....

(不确定它是否重要但目前90%的数据库行具有相同的Source,尽管这不会长久保持这样)

  1. 通过下面的查询,根本没有使用索引,我正在使用SQL Server 2014,而在执行计划中,它说它只使用聚集索引扫描:

    SELECT * 
    FROM [db].[dbo].[reports]
    WHERE Source = 'name1' 
      AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
    
  2. 使用此查询,根本不使用索引,虽然我从SQL Server得到一个建议,创建一个日期优先和源秒的索引...我读到索引应该是查询的顺序是什么?它还说要包括我选择的所有列,这是必须的吗?...再次我读到我应该只在索引中包含我正在搜索的列。

    SELECT * 
    FROM [db].[dbo].[reports]
    WHERE Source = 'name1' 
      AND ShortDate = '2017-10-13'
    

    SQL Server索引建议 -

    /* The Query Processor estimates that implementing the following 
       index could improve the query cost by 86.2728%. */
    
    /*
    USE [db]
    GO
    
    CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
    ON [dbo].[reports] ([ShortDate], [Source])
    INCLUDE ([id], [IsDuplicate], [IsNotValid], [Time], [Email])
    GO
    */
    
  3. 现在我尝试使用SQL Server建议我制作的索引并且它有效,似乎它使用上述查询的100%非聚集索引。

    我尝试使用此索引但删除了包含的列并且它不起作用...似乎我必须在索引中包含我正在选择的所有列?

    顺便说一下,如果我包含了所有列,那么在使用我制作的索引时也可以。

    总结一下:看起来索引的顺序无关紧要,因为它在创建Source + ShortDateShortDate + Source

    时都有效

    但由于某种原因,它必须包含所有列......(这将极大地影响对该表的写入?)

    非常感谢阅读,我的目标是了解为什么会发生这种事情以及我应该做些什么(不仅仅是解决方案,因为我需要将其应用于其他项目)。

    干杯:)

3 个答案:

答案 0 :(得分:7)

SQL Server中的索引是长期经验(以及许多小时的挫折)的部分专有技术,也是部分黑魔法。不要过多地打败自己 - 这就像SO这样的地方是理想的 - 很多大脑,经过多个小时的优化,你可以利用的经验。

  
    

我读到索引应该按查询的顺序排列?

  

如果您读到这一点 - 它绝对不正确 - 列的顺序相关 - 但以不同的方式:复合索引(由多个组成)如果您在查询中的索引定义中指定 n最左侧列,则只会考虑列。

经典示例:索引为(city,lastname,firstname)的电话簿。可以使用这样的索引

  • 在查询中指定其WHERE子句
  • 中的所有三列
  • 在使用citylastname的查询中(在“底特律”中找到所有“米勒”)
  • 或仅按城市过滤的查询

但是,如果您只想搜索firstname ..... 关于您需要的复合索引的技巧,它可以永远不会意识到。但是如果你总是使用索引中的所有列,它们的排序通常并不真正相关 - 查询优化器会为你处理这个。

至于包含的列 - 这些存储在非聚集索引的叶级别中 - 它们是 NOT 搜索的一部分索引的结构,您不能为WHERE子句中包含的列指定过滤器值。

这些包含列的主要好处是:如果您在非聚集索引中搜索,最后,您实际上找到了您正在寻找的值 - 那时您有什么可用的?非聚集索引将列存储在非聚集索引定义(ShortDateSource)中,它将存储聚类键(如果您有一个 - 并且您应该!) - 但没有别的。

因此,在这种情况下,一旦找到匹配项,并且您的查询需要该表中的所有内容,SQL Server必须执行所谓的密钥查找(通常也称为书签查找),其中它采用聚簇键,然后针对聚簇索引执行 Seek 操作,以获取包含所有内容的实际数据页你正在寻找的价值。

如果索引中包含包含列,则非群集索引的叶级页面包含

  • 非聚集索引中定义的列
  • 群集键列
  • INCLUDE声明
  • 中定义的所有其他列

如果这些列“覆盖”您的查询,例如提供查询所需的所有值,然后SQL Server在找到您在非聚簇索引中搜索的值后完成 - 它可以从非聚簇索引的叶级页面获取所需的所有值,并且不需要在聚类索引中执行另一个(昂贵的)键查找以获取实际值。

因此,尝试始终明确指定只有SELECT真正需要的列才有用 - 在这种情况下,你或许可以创建一个有效的覆盖索引,为您提供SELECT的所有值 - 始终使用SELECT *使得真的很难或几乎不可能。 ....

答案 1 :(得分:1)

通常,您希望索引从最具选择性(即过滤掉最可能的记录)到最不具有选择性;如果列的基数较低,查询优化器可能会忽略它。

这很直观 - 如果你有一本电话簿,并且你正在寻找名为&#34; smith&#34;的人,最初的&#34; A&#34;,你想要开始寻找&#34;史密斯&#34;首先,然后是&#34; A&#34; s,而不是所有初始为&#34; A&#34;然后过滤掉那些名为&#34; Smith&#34;。毕竟,可能性是26人中有一人拥有最初的&#34; A&#34;。

因此,在您的示例中,我猜您在短日期内拥有大量值 - 因此这是查询优化器尝试过滤掉的第一列。你说你在&#34; source&#34;中有几个不同的值,所以查询优化器可能决定忽略它;在这种情况下,该索引中的第二列也没用。

索引中where子句的顺序无关紧要 - 您可以将它们交换并获得完全相同的结果,因此查询优化器会忽略它们。

编辑:

所以,是的,制作索引。想象一下,你有一堆卡要排序 - 在你的第一次运行中,你想要删除尽可能多的卡。假设它全部均匀分布 - 如果你有超过一百万行的1000个单独的short_dates,这意味着如果你的第一次运行开始于short_date,你最终会得到1000个项目;如果按源排序,则有100000行。

答案 2 :(得分:0)

索引的包含列适用于您选择的列。 由于您执行select *(这不是一个好习惯),因此无法使用索引,因为它必须查找整个表以获取列的值。

对于您的场景,我会删除默认聚簇索引(如果有)并使用以下语句创建新的聚簇索引:

USE [db]
GO
CREATE CLUSTERED INDEX CIX_reports
    ON [dbo].[reports] ([ShortDate],[Source])
GO