T-SQL查询和C# - 当性能很重要时

时间:2014-06-10 15:39:02

标签: c# asp.net sql sql-server

好吧,无法想出一个非常好的方式在标题中表达这一点,但场景是这样的: -

您正在构建报告。该报告位于一个带有C#代码的aspx页面后面,该代码访问T-SQL数据库。

从中获取数据的表非常大(数百万行)。你需要在另一个表上进行查找的两列(Group和SuperGroup - 见下文),这个查找表恰好也是几万行(更不用说你实际上必须加入两个表)正确创建查找表 - 请参阅下面的#partGroups)

现在记住运行的页面会在2分钟后超时......

在此过程中必须做出几个假设: -

  1. 表格及其布局是不可变的,无论设计是坏还是其他,它们就是它们,你必须使用它们(Assets,CoreStockParts和CoreStockPartsGroups)。
  2. 不能更改页面超时。
  3. PartNumbers(资产中的Text01,CoreStockParts中的PartNo)可以,可以并且确实包含一个表中的 - 和/或空格而不包含另一个表中的空格,因此需要删除它们。
  4. Assets中的PartNumbers有时会在Assets中以字符为前缀,但不会在CoreStockParts中为前缀。
  5. 这是我到目前为止基本上得到的: -

        select rtrim(ltrim(Replace(Replace(csp.PartNo,' ',''), '-',''))) as PartNumber, 
            csp.[Description], csp.GroupCode, coalesce(cspg.[Group], 'Unknown') as [Group], coalesce(cspg.SuperGroup, 'Unknown') as SuperGroup
        into #partGroups
        from CoreStockParts as csp
            left join CoreStockPartsGroups as cspg on csp.GroupCode = cspg.Code
    
        select p.ID, 
            rtrim(Replace(Replace(p.Text01,' ',''), '-','')) as PartNumber1, 
            right(p.Text01, len(p.Text01)-1) as PartNumber2,
            p.Numeric01 as CostAmount, p.Numeric02 as SaleAmount, p.Numeric03 as ExtendedCostAmount, 
            p.Numeric04 as ExtendedSaleAmount, p.Numeric05 as Quantity, p.Date01 as InvoiceDate
        INTO #coreParts
        FROM Assets as p
        WHERE p.Category = 'PART'
        and len(p.Text01) > 0
    
        select ID, PartNumber1, PartNumber2, [Description], CostAmount, SaleAmount, ExtendedCostAmount, 
            ExtendedSaleAmount, Quantity, InvoiceDate, [Group], SuperGroup
        from #coreParts as cp
            inner join #partGroups as pg on cp.PartNumber1 = pg.PartNumber
        union
        select ID, PartNumber1, PartNumber2, [Description], CostAmount, SaleAmount, ExtendedCostAmount, 
            ExtendedSaleAmount, Quantity, InvoiceDate, [Group], SuperGroup
        from #coreParts as cp
            inner join #partGroups as pg on cp.PartNumber2 = pg.PartNumber
    

    目前服务器负载中等,大约需要1分45秒。仍然存在需要添加的限制,包括但不限于基于Group,SuperGroup的过滤和基于InvoiceDate的日期范围。最重要的是,一旦我终于获得了这些数据,我就需要开始在其中执行聚合函数,以生成各种Group / SuperGroups的销售数量/值等图表。

    现在我想,如果我能保持这种速度......尽管它很难理想。如果我能加速它那么棒!然而,任何超过15秒的东西,然而我们碰到了一堵墙。

    所以这个问题的症结在于我猜多了: -

    1. 我是否遗漏了一些明显可以做的事情来优化这一点?
    2. 此时将结果返回给C#和LINQ我需要的数字会更好吗?
    3. 我认为如果我在T-SQL中进行过滤,最好的位置是在临时表的选择中,而不是在最后一个语句中产生的混搭?
    4. 编辑:好的一些更新!

      首先,我对我所看到的内容的评估是错误的,我们已经获得了添加快照表的授权,该快照表可以完成所有工作,一起获取我们需要的数据,以便运行报告第二天的代码。

      特别感谢Blindy和user17594提供有关索引的输入和阻止使用索引的位。 (比特,那是技术语言,你知道8D)。

4 个答案:

答案 0 :(得分:3)

不要在asp页面中执行报告,而是使用后台服务:

  1. 创建一个等待查询的服务(通过管道,套接字,wcf,等等......)

  2. 当您需要报告时,如果该报告已经存在,请从我们的asp页面询问该服务,如果没有告诉该服务创建它。

  3. 向用户显示“正在加载”消息,并通过Ajax请求您的页面询问您的服务是否已准备好报告。

  4. 当服务“捣乱”所有数据时,通过您的asp页面显示您的报告/图表。

  5. 从网页中分离长期运行进程以避免超时并为用户留下一个看起来悬挂的页面总是一个好主意(假设一个非常紧张的用户因为页面花了2分钟而开始按F5渲染...你的服务器会因生成大量报告而崩溃。

答案 1 :(得分:2)

您可能希望尝试一些事情,看看哪些会起作用。请注意,如果需要进行任何更改,您需要对其进行测试,测试和测试,以确定它是否可以提高系统性能。

  • 使用UNION ALL而不是UNION删除与UNION关联的DISTINCT检查(除非您知道这将复制结果)
  • 或者,删除您的UNION并将您的联接替换为pg.PartNumber IN(cp.PartNumber1,cp.PartNumber2)
  • 检查您的指数。 and len(p.Text01) > 0将阻止使用索引查找。请尝试使用AND p.Text01 IS NOT NULL AND p.Text01 != ''
  • 尝试索引临时表CREATE UNIQUE CLUSTERED INDEX IX_partGroups_tmp ON #partGroups (PartNumber)以及CREATE NONCLUSTERED INDEX IX_coreParts_tmp ON #coreParts (PartNumber1,PartNumber2)

尝试将数据处理保留在数据库中,因为数据平台已针对此进行了优化。尽可能过滤,只返回您的客户所需的内容。

答案 2 :(得分:1)

另一个想法是创建一个报告数据服务。

这会运行现有数据,填充一个单独的数据库,其中包含针对报告进行了优化的结构,您的报告将在此上运行,这应该快几个数量级,因为所有繁重的工作都是由报告服务完成的。

一个缺点是新数据将是“陈旧的”#34;取决于更改填充到报告数据库的速度。许多企业都没有注意到或关注数据是否过时60或90秒,但这显然取决于您自己的用例。

答案 3 :(得分:0)

我的方法是将计算列添加到表中:

ALTER TABLE CoreStockParts 
ADD PartNumber AS RTRIM(LTRIM((REPLACE(REPLACE(PartNo,' ',''), '-','')));

ALTER TABLE CoreParts
ADD PartNumber1 AS RTRIM(REPLACE(REPLACE(p.Text01,' ',''), '-','')),
    PartNumber2 AS RIGHT(p.Text01, LEN(p.Text01)-1);

现在您已经拥有了这些计算列,您可以将它们编入索引:

CREATE NONCLUSTERED INDEX IX_CoreStockParts_PartNumber ON CoreStockParts (PartNumber)
    --INCLUDE ([Description], GroupCode)

不确定您的确切表格结构,但索引中包含非键列可能会有助于提高性能,如果您认为可以,则取消注释第二行。

CREATE NONCLUSTERED INDEX IX_CoreParts_PartNumber1__part ON CoreParts (PartNumber1)
    --WHERE Category = 'PART' AND LEN(Text01) > 0

CREATE NONCLUSTERED INDEX IX_CoreParts_PartNumber2__part ON CoreParts (PartNumber2)
    --WHERE Category = 'PART' AND LEN(Text01) > 0

根据您在其他地方使用该表的方式,此索引可能会从过滤索引中受益,如有必要,请再次取消注释。您可能还需要CoreStockPartsGroups.Code和'CoreStockParts.GroupCode'上的其他索引。

最后我不会使用临时表,通常最好避免它们,因为你失去了源表上现有索引的好处:

WITH PartGroups AS
(   SELECT  csp.PartNumber,
            csp.[Description], 
            csp.GroupCode, 
            ISNULL(cspg.[Group], 'Unknown') AS [Group], 
            ISNULL(cspg.SuperGroup, 'Unknown') AS SuperGroup
    FROM    CoreStockParts AS csp
            LEFT JOIN CoreStockPartsGroups AS cspg 
                ON csp.GroupCode = cspg.Code
)
SELECT  p.ID, 
        p.PartNumber1, 
        p.PartNumber2, 
        p.[Description], 
        CostAmount = p.Numeric01, 
        SaleAmount = p.Numeric02,
        ExtendedCostAmount = p.Numeric03, 
        ExtendedSaleAmount = p.Numeric04, 
        Quantity = p.Numeric05, 
        InvoiceDate = p.Date01, 
        pg.[Group], 
        pg.SuperGroup
FROM    Assets as p
        INNER JOIN partGroups AS pg 
            ON p.PartNumber1 = pg.PartNumber
WHERE   p.Category = 'PART'
AND     LEN(p.Text01) > 0 as cp
UNION 

SELECT  p.ID, 
        p.PartNumber1, 
        p.PartNumber2, 
        p.[Description], 
        CostAmount = p.Numeric01, 
        SaleAmount = p.Numeric02,
        ExtendedCostAmount = p.Numeric03, 
        ExtendedSaleAmount = p.Numeric04, 
        Quantity = p.Numeric05, 
        InvoiceDate = p.Date01, 
        pg.[Group], 
        pg.SuperGroup
FROM    Assets as p
        INNER JOIN partGroups AS pg 
            ON p.PartNumber2 = pg.PartNumber
WHERE   p.Category = 'PART'
AND     LEN(p.Text01) > 0 as cp;

在启用了显示实际计划的SSMS中运行将通过添加索引建议进一步改进。