【问题标题】:Stored procedure performance degradation with MIN and MAX functions使用 MIN 和 MAX 函数的存储过程性能下降
【发布时间】:2026-01-04 05:15:01
【问题描述】:

我在生产中有一个数据库表,用于存储给定项目的workflow;表中的每条记录基本上代表特定日期的项目状态。

过度简化的表结构是这样的:

工作流程表

|-------------|------------|---------|----------------|
| Category    | ItemCode   | Status  | InsertDate     |
|-------------|------------|---------|----------------|
|    Cat1     |    Foo1    | 01      | 2012-01-01     |
|-------------|------------|---------|----------------|
|    Cat1     |    Foo1    | 02      | 2012-03-02     |
|-------------|------------|---------|----------------|
|    Cat1     |    Foo1    | 03      | 2012-04-01     | 
|-------------|------------|---------|----------------|
|    Cat1     |    Foo2    | 01      | 2012-04-06     |
|-------------|------------|---------|----------------|
|    Cat1     |    Foo2    | 02      | 2012-05-07     |
|-------------|------------|---------|----------------|
|    Cat1     |    Foo2    | 04      | 2012-05-09     | 
|-------------|------------|---------|----------------|
|    Cat2     |    Foo3    | 01      | 2011-02-03     |    
|-------------|------------|---------|----------------|
|    ...      |    ...     | ..      |....            |    
|-------------|------------|---------|----------------|

因此,在 2012-01-01,项目 Foo1 已达到状态 01;在 2012-04-01 已达到状态 03 等等。

StoredProcedure PR_GetCategoryItemsInformation 以给定的Category 作为输入,读取工作流表并给出如下结果:

@输入:Cat1
输出:

|------------------|---------------|------------------|---------------------|
|   Category       |    ItemCode   | DateOfFirstRecord| StatusOfLatestRecord|
|------------------|---------------|------------------|---------------------|
|     Cat1         |     Foo1      |    2012-01-01    |         03          |    
|     Cat1         |     Foo2      |    2012-04-06    |         04          |

SP,给定一个Category,对于每个ItemCode需要获取工作流的第一行以读取InsertDate,并获取工作流的最后一行以获取当前的Status

它归结为如下所示的 SP 实现:

CREATE PROCEDURE dbo.PR_GetFooItemInformation
    @Category CHAR(3)
AS
BEGIN

    CREATE TABLE #TabTemp (
            Category CHAR(3),
        ItemCode CHAR(3),       
        Status CHAR(2), 
        InsertDate DATETIME
    )

    CREATE CLUSTERED INDEX XIE1TabTemp 
        ON #TabTemp (...)

    CREATE NONCLUSTERED INDEX XIE2TabTemp 
        ON #TabTemp (...)

    INSERT INTO #TabTemp 
    SELECT
                 Category,
                 ItemCode,
                 Status,
                 InsertDate   
    FROM Workflow
    WHERE (Some rules to cut down the number of rows)

  SELECT 
      T1.Category,
      Item.ItemCode,
      T1.InsertDate,
      T2.Status
  FROM 
      Item
  INNER JOIN
      #TabTemp as T1 ON Item.ItemCode = Workflow.ItemCode
  INNER JOIN 
      #TabTemp as T2 ON Item.ItemCode = Workflow.ItemCode
  WHERE
      ...
  AND
      T1.InsertDate= SELECT 
                         MIN(InsertDate) 
                     FROM 
                         #TabTemp as T3 
                     WHERE ..
  AND
      T2.InsertDate = SELECT 
                         MAX(InsertDate) 
                      FROM 
                         #TabTemp as T4 
                      WHERE ..

SP 多年来一直按预期工作(2005 年),但几个月前它开始出现随机超时;由于workflow 表的记录数在增长(250 万并且还在增加),它的性能肯定会越来越差*

这些表已正确编入索引,而且就其价值而言,sql 管理工作室不建议在 SP 上建立任何进一步的索引。
不使用临时表的同一个 SP 速度要慢 4 倍。
此时的临时表在每次调用时平均填充了 150 万行。

据我有限的 dba 知识,这个问题与 MINMAX 函数有关,需要计算这些函数才能到达给定类别的每个项目的第一行和最后一行。

我省略了有关工作流表和 SP 实现的一些细节,但我希望我所描述的内容足以了解问题。

最后一个问题:
你知道处理这种情况的任何 sql 策略甚至 sql-server 专有解决方案吗?

我有什么样的限制?
好吧,SP 用于 BackOffice 函数,应该返回所有实时记录,而不是预处理的子集。

* 我不是 dba;其中一位 dba 目前正在他黑暗的实验室里研究这个小怪物。

【问题讨论】:

  • 表是否有id字段,PRIMARY KEY?
  • 您知道减速是由于工作流表中的行数增加,还是由于插入临时表的行数?了解表上的索引以及它们是否已被维护/重建以减少碎片也很有帮助。您是否查看过此过程的(估计和/或实际)执行计划?

标签: sql sql-server sql-server-2008 stored-procedures timeout


【解决方案1】:

试试这个 -

SELECT Category,
       ItemCode,
       MIN(InsertDate),
       MAX(Status)
FROM workflow 
WHERE Category = @cat
GROUP BY ItemCode 

您可能不需要临时表。此查询将获得所需的输出。 索引 Category 和 ItemCode。

【讨论】:

    【解决方案2】:

    插入日期是索引吗?您需要一个复合索引(类别、项目代码、插入日期)

    您的瓶颈很可能在您不必要地创建的临时表中。您可以使用 where 条件过滤掉您的行。

    是否可以像这样重写您的查询?

    select category, itemcode, a.InsertDate, b.Status from (
            select category, itemCode, min(InsertDate) minDate, max(insertDate) maxDate
            from table where .. group by categroy, item code) minmax
            join table a on a.category =minmax.category 
                      and a.itemcode=minmax.itemcode and a.insertDate = minmax.mindate
            join table b on b.category =minmax.category and b.itemcode=minmax.itemcode 
            and b.insertDate = minmax.max date) results
    

    【讨论】:

    • 没有临时表,SP 会慢 4 倍。
    【解决方案3】:

    您建议的转换可以通过一个相对简单的查询来完成:

    select category, ItemCode, min(InsertDate) as DateOfFirstRecord,
           max(case when seqnum = 1 then Status end) as LastStatus           
    from (Select category, ItemCode, Status, InsertDate,
                 row_number() over (partition by category, ItemCode order by InsertDate desc) as seqnum
          from workflow w
          where category = <category>  
         )  w
    group by category, ItemCode;
    

    我意识到,一旦你加入你的条件,这会变得更加复杂。

    一般来说,我更喜欢让 SQL 优化器选择执行查询的最佳方式,而不是使用临时表。 (话虽如此,有一些非常不愉快的经历,我不得不求助于多个查询,因为优化器选择了错误的计划。)

    我建议您尝试一下,看看它是否能解决您的性能问题。

    【讨论】:

    • 我正在遵循这个提示,很快我会发布结果
    【解决方案4】:
    SELECT  *
    FROM    item
    CROSS APPLY
            (
            SELECT  MIN(insertDate) AS dateOfFirstRecord
            FROM    workflow wf
            WHERE   wf.itemCode = i.itemCode
            ) fr
    OUTER APPLY
            (
            SELECT  TOP 1
                    status AS statusOfLatestRecord
            FROM    workflow wf
            WHERE   wf.itemCode = i.itemCode
            ORDER BY
                    wf.insertDate DESC
            ) lr
    

    workflow (itemCode, insertDate) 上创建一个索引以使其快速工作。

    【讨论】:

    【解决方案5】:

    为什么你必须计算超过日期的 MAX 和 MIN?

    你可以做到 MAX

     SELECT TOP 1 InsertDate FROM #TabTemp WHERE ... ORDER BY InsertDate DESC
    

    对于 MIN

     SELECT TOP 1 InsertDate FROM #TabTemp WHERE ... ORDER BY InsertDate ASC
    

    并将其保存到 2 个日期时间变量中。

    【讨论】:

    • 您确定这会比 MIN 和 MAX 更好地扩展吗?这些表已编入索引,因此我希望使用 TOP 和 MAX 获得相同的性能。