SQL Server性能和索引视图
使用SQL Server 2008的SQL Server性能和索引视图
(很抱歉,如果这原来是一篇文章,但我想给尽可能多的信息越好。)
我有多个位置其中每个部门都包含多个部门,每个部门都包含多个可以进行零到多次扫描的项目。每次扫描都涉及可能会或可能不会有中断时间的特定操作。每个项目也属于一个特定的包,属于特定的工作,属于特定的项目,属于特定的客户。每个作业都包含一个或多个包含一个或多个项目的包。
+=============+ +=============+
| Projects | --> | Clients |
+=============+ +=============+
^
|
+=============+ +=============+
| Locations | | Jobs |
+=============+ +=============+
^ ^
| |
+=============+ +=============+ +=============+
| Departments | <-- | Items | --> | Packages |
+=============+ +=============+ +=============+
^
|
+=============+ +=============+
| Scans | --> | Operations |
+=============+ +=============+
项目表中大约有24,000,000条记录,扫描表中大约有48,000,000条记录。新项目偶尔会全天插入到数据库中,通常数以万计的流行时间。新扫描每小时插入一次,每个插入的数量从几百到几十万不等。
这些表格被严格查询,切片和切块每一个方式。我正在编写非常具体的存储过程,但它变成了维护的噩梦,因为我处在一个没有终点的网站中的一百个存储过程的边缘(例如类似于ScansGetDistinctCountByProjectIDByDepartmentIDGroupedByLocationID,ScansGetDistinctCountByPackageIDByDepartmentIDGroupedByLocationID等)。幸运的是,需求几乎每天都会改变(感觉),每次我必须更改/添加/删除一列,那么......我最终在酒吧。
所以我创建了一个索引视图和一些带有参数的通用存储过程,以确定过滤和分组。不幸的是,性能下降了。 我想第一个问题是,因为选择性能是最重要的,我应该坚持具体的方法,并通过改变基础表来对抗吗?或者,可以做些什么来加速索引视图/通用查询方法?除了缓解维护噩梦之外,我实际上希望索引视图能够提高性能。
下面是生成视图的代码:
CREATE VIEW [ItemScans] WITH SCHEMABINDING AS
SELECT
p.ClientID
, p.ID AS [ProjectID]
, j.ID AS [JobID]
, pkg.ID AS [PackageID]
, i.ID AS [ItemID]
, s.ID AS [ScanID]
, s.DateTime
, o.Code
, o.Cutoff
, d.ID AS [DepartmentID]
, d.LocationID
-- other columns
FROM
[Projects] AS p
INNER JOIN [Jobs] AS j
ON p.ID = j.ProjectID
INNER JOIN [Packages] AS pkg
ON j.ID = pkg.JobID
INNER JOIN [Items] AS i
ON pkg.ID = i.PackageID
INNER JOIN [Scans] AS s
ON i.ID = s.ItemID
INNER JOIN [Operations] AS o
ON s.OperationID = o.ID
INNER JOIN [Departments] AS d
ON i.DepartmentID = d.ID;
和聚簇索引:
CREATE UNIQUE CLUSTERED INDEX [IDX_ItemScans] ON [ItemScans]
(
[PackageID] ASC,
[ItemID] ASC,
[ScanID] ASC
)
这里的通用存储的特效之一。它获取已扫描并有一个截止的项目数:
PROCEDURE [ItemsGetFinalizedCount]
@FilterBy int = NULL
, @ID int = NULL
, @FilterBy2 int = NULL
, @ID2 sql_variant = NULL
, @GroupBy int = NULL
WITH RECOMPILE
AS
BEGIN
SELECT
CASE @GroupBy
WHEN 1 THEN
CONVERT(sql_variant, LocationID)
WHEN 2 THEN
CONVERT(sql_variant, DepartmentID)
-- other cases
END AS [ID]
, COUNT(DISTINCT ItemID) AS [COUNT]
FROM
[ItemScans] WITH (NOEXPAND)
WHERE
(@ID IS NULL OR
@ID = CASE @FilterBy
WHEN 1 THEN
ClientID
WHEN 2 THEN
ProjectID
-- other cases
END)
AND (@ID2 IS NULL OR
@ID2 = CASE @FilterBy2
WHEN 1 THEN
CONVERT(sql_variant, ClientID)
WHEN 2 THEN
CONVERT(sql_variant, ProjectID)
-- other cases
END)
AND Cutoff IS NOT NULL
GROUP BY
CASE @GroupBy
WHEN 1 THEN
CONVERT(sql_variant, LocationID)
WHEN 2 THEN
CONVERT(sql_variant, DepartmentID)
-- other cases
END
END
我第一次运行查询,看了看实际的执行计划,我创建的缺失索引,它建议:
CREATE NONCLUSTERED INDEX [IX_ItemScans_Counts] ON [ItemScans]
(
[Cutoff] ASC
)
INCLUDE ([ClientID],[ProjectID],[JobID],[ItemID],[SegmentID],[DepartmentID],[LocationID])
创建索引会将执行时间缩短到大约五秒钟,但仍然不可接受(查询的“特定”版本在亚秒级运行。)我尝试向索引中添加不同的列,而不是仅将它们包括在内获得成绩(并没有真正的帮助,我不知道我现在在做什么。)
下面是查询计划:
这里是为第一指标的细节寻求(它似乎返回所有行的视图,其中截止IS NOT NULL):
的通用PROC可能无法在这种情况下,一个坏主意,但你不必把所有这些案件到最终的查询,你正在做的事情。我会尝试建立你的“具体问题”使用动态SQL在通用PROC,几乎相同的方式盖尔肖建立一个“包罗万象”的查询这里:
SQL in the Wild - Catch-all queries
这样,你能缓存查询计划并利用博客文章中显示的索引,并且您应该能够获得与之前相同的亚秒级性能。
感谢您的快速响应。我不得不承认,我从来没有考虑动态SQL。我知道这是一个时间和地点,但我仍然有“动态SQL永远是邪恶的”在我脑海中回荡。似乎无法动摇它。 – Frank 2013-05-01 19:20:41
带参数的'sp_executesql'提供了很多procs的性能,并且可以避免大部分'exec(@sql)'的恐怖。我认为“必须...使用... PROCS!”的日子很大程度上落后于我们,但不幸的是,我仍然在许多数据库中保留了数百个数据库。 – 2013-05-01 19:32:57
你会喜欢这个......如果你看看我的存储过程,在声明参数后,我有WITH RECOMPILE。如果我删除它并在存储过程的末尾添加OPTION(RECOMPILE),它会飞行。我没有足够的智慧去了解它们之间的差异,但是我很高兴你发布了这个链接,把我带到了Erland Sommarskog的网站,我注意到了它的不同之处。另一个原因是,当人们指引我朝着正确的方向而不是告诉我如何做某件事时,我更喜欢它。再次感谢。 – Frank 2013-05-01 20:16:52