4

使用 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 的所有行):

手术

4

1 回答 1

3

在这种情况下,通用 proc 可能不是一个坏主意,但您不必像当前所做的那样将所有这些情况都放入最终查询中。我会尝试在您的通用 proc 中使用动态 SQL 构建您的“特定查询”,就像 Gail Shaw 在此处构建“catch-all”查询一样:

狂野的 SQL - 包罗万象的查询

通过这种方式,您可以缓存查询计划并利用博客文章中所示的索引,并且您应该能够获得与您所追求的相同的亚秒级性能。

于 2013-05-01T18:56:49.100 回答