几天来,我一直在努力提高我的数据库的性能,对于 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'
据我所知,在不影响编写时间的情况下提高效率的最佳方法是在 Source
和 ShortDate
上创建非聚集索引。
我喜欢这样的索引模式:
CREATE NONCLUSTERED INDEX [Source&Time]
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)
现在我们进入了让我完全迷失的棘手部分,上面的索引有时有效,有时半有效,有时根本无效....
(不确定这是否重要,但目前 90% 的数据库行具有相同的源,尽管这种情况不会持续很长时间)
对于下面的查询,根本没有使用索引,我使用的是 SQL Server 2014,在执行计划中它说它只使用聚集索引扫描:
SELECT * FROM [db].[dbo].[reports] WHERE Source = 'name1' AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
在这个查询中,索引根本没有被使用,尽管我从 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 */
现在我尝试使用 SQL Server 建议我创建的索引并且它有效,看起来它使用上述两个查询使用 100% 的非聚集索引。
我尝试使用此索引,但删除了包含的列但它不起作用...似乎我必须在索引中包含我正在选择的所有列?
顺便说一句,如果我包含所有列,它也可以在使用我创建的索引时使用。
总而言之:索引的顺序似乎并不重要,因为它在创建 Source + ShortDate
和 ShortDate + Source
时都有效
但出于某种原因,必须包含所有列...(这将极大地影响写入此表?)
非常感谢阅读,我的目标是了解为什么会发生这种情况以及我应该做些什么(不仅仅是解决方案,因为我还需要将其应用到其他项目中)。
干杯:)
最佳答案
SQL Server 中的索引部分是长期经验(以及许多小时的挫折)的专有技术,部分是黑魔法。不要为此自责太多 - 这就是像 SO 这样的地方的理想之处 - 大量的大脑,大量的优化经验,您可以利用它们。
I read that the index should be made by the order the query is?
如果您读到这篇文章 - 它绝对不正确 - 列的顺序是相关的 - 但以不同的方式:复合索引(由多个列)只有在您在查询的索引定义中指定n 最左边的列时才会被考虑。
经典示例:电话簿的索引为(城市、姓氏、名字)。这样的索引可能会被使用:
- 在其
WHERE
子句中指定所有三列的查询中 - 在使用
city
和lastname
的查询中(查找“Detroit”中的所有“Miller”) - 或在仅按城市过滤的查询中
但如果您只想搜索firstname
,则可以NEVER EVER使用它......这就是复合索引的技巧你需要知道。但是,如果您总是使用索引中的所有列,那么它们的顺序通常并不真正相关 - 查询优化器会为您处理这个问题。
至于包含的列 - 它们仅存储在非聚集索引的叶级中 - 它们不是搜索的一部分索引的结构,并且您不能为 WHERE
子句中包含的那些列指定过滤器值。
这些包含的列的主要好处是:如果您在非聚集索引中搜索,最后您实际上找到了您正在寻找的值 - 此时您有什么可用的?非聚集索引将存储非聚集索引定义中的列(ShortDate
和 Source
),并将存储聚集键(如果你有一个 - 你应该!) - 但没有别的。
因此在这种情况下,一旦找到匹配项,并且您的查询需要该表中的所有内容,SQL Server 必须执行所谓的键查找(通常也称为书签查找),它获取聚集键,然后对聚集索引执行Seek操作,以到达包含所有内容的实际数据页您正在寻找的值。
如果您的索引中有包含的列,那么您的非聚集索引的叶级页面包含
- 非聚集索引中定义的列
- 集群键列
- 所有那些额外的列在您的
INCLUDE
语句中定义
如果这些列“涵盖”了您的查询,例如提供查询所需的所有值,然后 SQL Server 在找到您在非聚集索引中搜索的值后完成 - 它可以从非聚集索引的叶级页面中获取所需的所有值,并且它 不需要对聚类索引进行另一次(昂贵的)键查找以获取实际值。
正因为如此,在您的 SELECT
中尝试始终明确指定您真正需要的那些列可能是有益的 - 在这种情况下,您也许能够创建一个高效的覆盖索引,为您的SELECT
提供所有值 - 始终使用SELECT *
使这变得非常困难或几乎不可能......
关于日期范围查询的 SQL 索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46766798/