SQL 日期范围查询索引

SQL index for date range query

几天来,我一直在努力提高我的数据库的性能,关于 SQL 服务器数据库中的索引,我仍然对一些问题感到困惑。

我会尽量提供信息。

我的数据库目前包含大约 10 万行,并且会继续增长,因此我正在努力寻找一种方法使其运行得更快。

我也在写信给这个table,所以如果你的建议会大大减少写作时间,请告诉我。

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

这通常是 select 超过 3,000 行,哈哈 ...

Table 架构:

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% 的数据库行具有相同的源,尽管这种情况不会持续很长时间)

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

    SELECT * 
    FROM [db].[dbo].[reports]
    WHERE Source = 'name1' 
      AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
    
  2. 在这个查询中,索引根本没有被使用,尽管我从 SQL 服务器得到一个建议,创建一个日期在前,源在后的索引。 .我读到索引应该按查询的顺序制作?它还说要包含我 selecting 中的所有列,这是必须的吗?...我再次读到我应该只在索引中包含我正在搜索的列。

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

    SQL 服务器索引建议 -

    /* 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
    */
    

现在我尝试使用索引 SQL 服务器建议我创建并且它有效,似乎它使用上面两个查询使用 100% 的非聚集索引。

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

顺便说一句,如果我包含所有列,它也可以在使用我创建的索引时使用。

总而言之:索引的顺序似乎并不重要,因为它在创建 Source + ShortDateShortDate + Source

时都有效

但出于某种原因,必须包含所有列...(这将极大地影响对此 table 的写作?)

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

干杯:)

索引的包含列适用于您选择的列。 由于您执行 select * (这不是好的做法),因此不会使用索引,因为它必须查找整个 table 才能获取列的值。

对于您的方案,我将删除默认的聚簇索引(如果有的话)并使用以下语句创建一个新的聚簇索引:

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

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

这很直观 - 如果您有一本 phone 的书,并且您正在寻找名为 "smith" 的人,首字母为 "A",您希望从搜索开始首先是"smith",然后是"A",而不是所有首字母为"A"的人,然后过滤掉那些叫"Smith"的人。毕竟,概率是每 26 个人中就有一个人拥有最初的 "A".

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

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

编辑:

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

SQL 服务器中的索引部分是长期经验(以及许多小时的挫折)的专有技术,部分是黑魔法。不要因为太多而自责 - 这就是像 SO 这样的地方的理想选择 - 大量的大脑,大量的优化经验,你可以利用。

I read that the index should be made by the order the query is?

如果你读到这个 ​​- 它绝对 NOT TRUE - 列的顺序 相关的 - 但以不同的方式:仅当您在查询的索引定义中指定 n 最左边的列 时,才会考虑复合索引(由多列组成)。

经典示例:一本 phone 的书,其索引为(城市、姓氏、名字)。这样的索引可能会用到:

  • 在其 WHERE 子句中指定所有三列的查询中
  • 在使用 citylastname 的查询中(查找 "Detroit" 中的所有 "Miller")
  • 或在仅按城市过滤的查询中

但如果您只想搜索 firstname ..... 那是,则可以永远使用它您需要了解的有关复合索引的技巧。但是,如果您总是使用索引中的所有列,那么它们的顺序通常并不真正相关 - 查询优化器会为您处理。


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

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

所以在这种情况下,一旦找到匹配项,并且您的查询需要 table 的所有内容,SQL 服务器必须执行以下操作称为 键查找 (通常也称为 书签查找 ),其中它获取聚集键然后执行 Seek 对聚簇索引的操作,以获取包含您要查找的所有值的实际数据页。

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

  • 非聚集索引中定义的列
  • 集群键列
  • 您的 INCLUDE 语句中定义的所有其他列

如果这些列 "cover" 您的查询,例如提供您的查询需要的所有值,然后 SQL 服务器一旦找到您在非聚集索引中搜索的值就完成了 - 它可以从非聚集索引的叶级页面中获取它需要的所有值,并且它不需要对聚类索引进行另一次(昂贵的)键查找以获取实际值。

因此,尝试总是明确指定只有那些你真正需要的列在你的SELECT中可能是有益的- 在这种情况下,您 可能能够 创建一个有效的 覆盖索引 为您的 SELECT 提供所有值 - 始终使用SELECT * 使这变得非常困难或几乎不可能.....