使用 SQL Server 2008。
(对不起,如果这是一篇文章,但我试图提供尽可能多的信息。)
我有多个位置,每个位置都包含多个部门,每个部门都包含多个可以进行零到多次扫描的项目。每次扫描都与可能有也可能没有截止时间的特定操作相关。每个项目也属于一个特定的包,属于一个特定的工作,属于一个特定的项目,属于一个特定的客户。每个作业包含一个或多个包,其中包含一个或多个项目。
+=============+ +=============+
| Projects | --> | Clients |
+=============+ +=============+
^
|
+=============+ +=============+
| Locations | | Jobs |
+=============+ +=============+
^ ^
| |
+=============+ +=============+ +=============+
| Departments | <-- | Items | --> | Packages |
+=============+ +=============+ +=============+
^
|
+=============+ +=============+
| Scans | --> | Operations |
+=============+ +=============+
items 表中大约有 24,000,000 条记录,scans 表中大约有 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])
创建索引将执行时间缩短到大约 5 秒,但这仍然是不可接受的(查询的“特定”版本运行亚秒级。)我尝试向索引添加不同的列,而不是仅仅包括它们而没有性能提升(在这一点上我不知道我在做什么并没有真正的帮助。)
这是查询计划:
以下是第一次索引查找的详细信息(它似乎返回了视图中 Cutoff IS NOT NULL 的所有行):