2009-07-09 189 views
6

我有一个关于SQL Server索引的问题。我不是DBA,并且假设对于那些你来说答案是清楚的。我使用SQL Server 2008的SQL Server索引顺序(日期时间字段)

我有一个表,它是类似于以下(但有更多列):

CREATE TABLE [dbo].[Results](
    [ResultID] [int] IDENTITY(1,1) NOT NULL, 
    [TypeID] [int] NOT NULL, 
    [ItemID] [int] NOT NULL, 
    [QueryTime] [datetime] NOT NULL, 
    [ResultTypeID] [int] NOT NULL, 
    [QueryDay] AS (datepart(day,[querytime])) PERSISTED, 
    [QueryMonth] AS (datepart(month,[querytime])) PERSISTED, 
    [QueryYear] AS (datepart(year,[querytime])) PERSISTED, 
CONSTRAINT [PK_Results] PRIMARY KEY CLUSTERED 
(
    [ResultID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] 
) ON [PRIMARY] 

的重要领域这里需要注意的是ResultID,主键,和QueryTime结果产生的日期时间。

我也有以下指标(其中包括):

CREATE NONCLUSTERED INDEX [IDX_ResultDate] ON [dbo].[Results] 
(
    [QueryTime] ASC 
) 
INCLUDE ([ResultID], 
[ItemID], 
[TypeID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] 

在一个数据库,在那里我有表中的一百万行,做一个查询,例如,当使用索引

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc 

在同一个数据库的另一个实例中,有5千万行,SQL Server决定不使用索引,而是使用聚集索引扫描,结果是速度非常慢。 (速度取决于日期)。即使我使用查询提示来使其使用IDX_ResultDate,它仍然有点慢,它的时间花费了94%的时间由ResultID排序。我想通过创建一个带有ResultID和QueryTime的索引作为索引中的已排序列,我可以加快查询速度。

因此,我创建了以下内容:

CREATE NONCLUSTERED INDEX [IDX_ResultDate2] ON [dbo].[Results] 
(
[QueryTime] ASC,  
[ResultID] ASC 
) 
INCLUDE ([ItemID], 
[TypeID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] 
GO 

我认为它会首先使用排序QueryTime找到匹配的结果,这已经ResultID进行排序。然而,情况并非如此,因为这个指数并没有改变现有指标的表现。

我然后尝试了以下指标:

CREATE NONCLUSTERED INDEX [IDX_ResultDate3] ON [dbo].[Results] 
(
    [ResultID] ASC, 
    [QueryTime] ASC 
) 
INCLUDE ([ItemID], 
[TypeID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] 
GO 

这一个产生预期的结果。它似乎以不变的时间(几分之一秒)返回。

但是,我很疑惑为什么IDX_ResultDate3工作正常,而IDX_ResultDate2不工作。

我会假设在QueryTime的排序列表中进行二分搜索,然后在结果ID的子列表中查看第一个结果,这是获取结果的最快方法。 (因此,我最初的排序顺序)。

旁边的问题:我应该创建一个持续的列,其中的QueryTime和索引的日期部分,而不是(我已经有三个持久列,你可以看到上面)?

回答

12

我会假设在 的二进制搜索作为QueryTime的排序列表后跟 通过偷看第一个结果 ResultIDs子列表是获取结果中最快的 方式。 (因此我的 初始排序顺序)。

这将是确实快,但是你的查询表达不同的请求:您所要求的与所有查询的最小ResultId ,经过“2009-05-01”发生的结果。为了满足请求,它必须在范围的起始处('2009-05-01')寻找,从这个位置开始扫描以提取所有的ResultId,对它们进行排序然后返回前1(最小ResultId)。你添加的第二个索引[idx_ResultDate2]也没有多大帮助。查询必须执行几乎完全相同的查找和扫描:结果ID在结果日期中排序为,因此要查找来自的最高ResultId,所有结果在'2009-05-01'之后,查询仍然必须扫描索引直到结束。

在最后一个索引[IDX_ResultDate3]上,该查询是作弊的。它的功能是开始扫描inde并查看QueryTime值,知道在此索引中扫描第一个具有所需范围(>'2009-05-01')中的QueryTime的结果是一个你想要的(因为ResultId保证是前1名)。您以纯粹的运气得到结果的“零点几分”:您在索引的开头有一个匹配的结果。查询可能会扫描整个索引并匹配非常差的结果。您可以插入一个QueryTime类似于'2010-01-01'的新结果,然后寻找它,您将看到性能下降,因为查询必须扫描整个索引直到结束(仍然比表扫描更快,因为更窄的索引尺寸)。

我的问题是:你绝对肯定你的查询必须在ORDER BY ResultID中返回TOP 1吗?或者你只是随便挑选了订单?如果您可以将ORDER BY请求更改为QueryTime,则任何索引(已更新:将QueryTime作为最左列)将返回简单的查找和提取,不扫描并且不进行排序。

+0

非常好的解释。我现在知道了。我会看看我是否可以重新设计应用程序以使用QueryTime排序。 – 2009-07-09 22:00:45

2

您可以更改聚集索引([QueryTime],[ResultID]),或从

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc 

改变你的查询

select top 1 <only the columns you actually need> from results where querytime>'2009-05-01' order by ResultID asc 

,包括在[IDX_ResultDate2]

所有这些列
+1

+1正好 - 拍摄包含满足查询所需的所有字段的“覆盖”索引(如果可能的话) – 2009-07-09 18:30:51

+0

是的,已经这样做了(不是在这里发布),而是同类的性能。 – 2009-07-09 22:01:25

4

你有一个范围一个字段的过滤条件以及ORDER BY另一个字段。

在这种情况下,索引即使是复合索引也不能用于满足这两个条件。

当您在(queryTime, resultId)上创建索引时,该索引用于过滤。引擎仍然需要订购结果集。

当您在(resultId, queryTime)上创建索引时,该索引用于排序。

由于您需要一个TOP 1结果,并且令人满意的结果恰好在索引的开头,所以后一种方法表现更好。

如果你的过滤条件是选择性(即它会返回几行),并且你需要的第一个结果恰好在索引的末尾,那么第一个结果会更好。

请参阅本文中我的博客更多的解释和提示上指数创造一个条件:

+0

不错的博客文章。 – 2009-07-09 21:57:36

0

我会建议的第一件事是检查统计对于此表(所有索引)是最新的。

由于您使用不同的数据集得到两个不同的执行计划,因此似乎SQL Server在挑选一个执行计划时出现臭名昭着的“判断调用”。

我同意Remus的解释,说明你为什么用最后一个索引获得“神奇”结果。

他的建议也不错 - 你真的想要通过resultID命令吗?或者,如果您可以通过queryTime进行排序,那么执行计划将能够使用索引顺序作为结果集的顺序(AND将通过索引与扫描进行比较),您将获得更多的性能。

+0

是的,统计数据是最新的。 (是的,它需要排序...不幸!) – 2009-07-09 22:02:18

0

我不确定我能否回答这个问题,但会指出聚簇索引键已经作为任何其他索引的一部分包含在内,所以它的冗余包含ResultID作为您建议的其他任何索引的一部分。

相关问题