Thi*_*ger 11 index sql-server nonclustered-index
我有一个超过 30 亿行的 SQL Server 表。我的一个查询需要很长时间,所以我正在考虑优化它。查询如下所示:
SELECT [Enroll_Date]
,Count(*) AS [Record #]
,Count(Distinct UserID) AS [User #]
FROM UserTable
GROUP BY [Enroll_Date]
Run Code Online (Sandbox Code Playgroud)
[Enroll_Date] 是具有少于 50 个可能值的低选择性列,而 UserID 列是具有超过 2 亿个不同值的高选择性列。根据我的研究,我认为我应该在这两列上创建一个非聚集复合索引,理论上高选择性列应该是第一列。但我不确定在我的情况下,这是否有效,因为我在 group by 子句中使用了低选择性列。
该表没有聚集索引。
Dan*_*her 12
作为@AaronBertrand 解决方案的替代方案(如果您不能或不想创建索引视图),我建议您在(Enroll_Date, UserID). 如果这种类型的问题在您的表中很常见,那么这甚至应该是您的聚集索引。
我通常不会推荐高选择性索引作为一般的“最佳实践”,而是查看哪种索引可以为您的查询提供最佳性能。
索引(Enroll_Date, UserID)将使用流聚合为您的查询提供高度优化的非阻塞查询计划。
在这种情况下,“非阻塞”意味着查询不需要缓冲任何大量数据(例如,排序或散列聚合),这意味着它 (a) 立即开始返回行,并且 ( b) 几乎不消耗工作记忆。
Aar*_*and 11
听起来像是索引视图的理想场景,它允许您在写入时而不是查询时为计算和聚合付费。
CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS
SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
FROM dbo.UserTable
GROUP BY Enroll_Date, UserID;
GO
CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);
Run Code Online (Sandbox Code Playgroud)
这将需要一些时间来创建,当然需要在所有 DML 操作中进行维护,就像基表上的索引一样。
现在针对此视图的查询将非常相似 - 视图中的每一行现在代表一个不同的用户/日期组合,因此该数字可以通过单个 COUNT(*) 计算,而基表中的总行数为已经为您部分汇总,现在您只需要使用 SUM 每个日期将它们相加:
SELECT Enroll_Date,
[Record #] = SUM(RawCount),
[User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date;
Run Code Online (Sandbox Code Playgroud)
我可以毫无疑问地告诉您,此查询将比您当前的查询快(但不会快多少),除非在极少数情况下每个日期只有一个用户(在这种情况下,将有相同数量的数据)要读取),我们知道的列是基表索引中唯一的列。读取时的性能提升是否值得影响工作负载写入部分的额外工作是我们无法告诉您的 - 您必须对其进行测试以衡量权衡(没有索引是免费的)。
如果您经常针对特定的、明确定义的范围(例如,当前季度或年初至今)对 Enroll_Date 使用相同的通用 WHERE 子句,则可以添加匹配的过滤索引以进一步减少该 I/O(但总是有一个权衡)。
您还可以考虑在基表上放置聚集索引。这似乎不是从堆中受益的那些非常罕见的用例之一。
usr*_*usr 11
Aarons 的回答是一个很好的解决方案。假设您不想采用这种方法,我会回答这个问题。
您发布的查询通常将首先分组执行(Enroll_Date, UserID),然后再分组(Enroll_Date)。此优化是 SQL Server 2012 的新增功能。它在单个COUNT DISTINCT.
以特定顺序在这两列上建立索引就(Enroll_Date, UserID)足以得到一个有效的计划,将索引扫描汇集到两个连续的流聚合中。相反的顺序不会启用该计划。
因此,请使用订单(Enroll_Date, UserID)。你在这里别无选择。