我对SQL Server索引有疑问。我不是DBA,并且假设你的答案很明确。我正在使用SQL Server 2008.
我有一个类似于以下的表(但有更多列):
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
在具有5000万行的同一数据库的另一个实例中,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的排序列表中进行二进制搜索,然后查看ResultID的子列表中的第一个结果是获取结果的最快方法。 (因此我的初始排序顺序)。
附带问题:我应该创建一个持久化的列,其中包含QueryTime的日期部分和索引(我已经有三个持久列,如上所示)?
答案 0 :(得分:12)
我会假设二进制搜索 作为QueryTime的排序列表 通过窥视它的第一个结果 ResultID的子列表是最快的 获得结果的方式。 (因此我的 初始排序顺序)。
这确实很快,但您的查询表达了不同的请求:您要求“2009-05-01”之后发生的所有查询中具有最小ResultId 的结果。为了满足它必须在范围的开始处寻求的请求('2009-05-01'),从该位置开始扫描以提取所有ResultId,对它们进行排序然后返回前1(最小ResultId)。您添加的第二个索引[idx_ResultDate2]也没有多大帮助。查询必须执行几乎完全相同的搜索和扫描:ResultIds在结果日期中排序,以便从所有结果中找出最佳ResultId '2009-05-01'查询仍然需要扫描索引直到结束。
在您的上一个索引[IDX_ResultDate3]上,查询是作弊的。它的作用是启动对inde的扫描并查看QueryTime值,知道在此索引中扫描具有所需范围的QueryTime的第一个结果(>'2009-05- 01')是你想要的那个(因为ResultId保证是Top 1)。您可以从纯粹的运气中获得“一秒钟”的结果:您在索引的开头有一个匹配的结果。查询可能会扫描整个索引并匹配非常lat的结果。您可以使用类似'2010-01-01'的QueryTime插入新结果,然后搜索它,您将看到性能下降,因为查询必须扫描整个索引直到结束(仍然比表扫描更快,因为缩小索引大小)。
我的问题是:您是否绝对肯定您的查询必须在ORDER BY ResultID中返回TOP 1?或者您只是随意选择了订单?如果您可以将ORDER BY请求更改为,例如,QueryTime,那么任何索引(已更新:将QueryTime作为最左侧列)将返回一个简单的Seek and Fetch,no scansn和no sorting。< / p>
答案 1 :(得分:4)
您在一个字段上有一个范围过滤条件以及ORDER BY
另一个字段。
在这种情况下,索引(甚至是复合索引)不能用于同时满足这两个条件。
在(queryTime, resultId)
上创建索引时,索引用于过滤。引擎仍然需要对结果集进行排序。
在(resultId, queryTime)
上创建索引时,索引用于排序。
由于您需要TOP 1
结果,并且满足此结果的行恰好位于索引的开头,后一种方法的效果会更好。
如果您的过滤条件是选择性(即它会返回几行),并且您需要的第一个结果恰好在索引的末尾,那么第一个分析会更好。
在我的博客中查看这篇文章,了解在哪些条件下创建哪个索引的更多解释和提示:
答案 2 :(得分: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]
中包含所有这些列答案 3 :(得分:0)
我建议的第一件事是检查此表的统计信息(所有索引)是否是最新的。
由于您获得了两个具有不同数据集的不同执行计划,因此SQL Server似乎在选择一个执行计划而不是另一个执行计划时做出了臭名昭着的“判断调用”。
我同意Remus的解释,为什么你的上一个索引会得到“神奇”的结果。
他的建议也很好 - 你真的想通过resultID订购吗?或者,如果您可以通过queryTime进行排序,那么您将获得更好的性能,因为执行计划将能够使用索引顺序作为结果集的顺序(并且它将搜索索引,而不是扫描)。
答案 4 :(得分:0)
我不确定我是否可以回答这个问题但是会指出聚集索引键已经作为任何其他索引的一部分包含在内,因此将其作为您提议的任何其他索引的一部分包含ResultID是多余的。 / p>