【问题标题】:Performance issue with stored procedure [closed]存储过程的性能问题[关闭]
【发布时间】:2016-02-13 07:17:24
【问题描述】:

我有一个使用 while 循环临时表和游标的存储过程,通过它我可以获得客户的老化余额,但是我的 SP 工作正常,但我有一些性能问题,因为它需要 15 秒才能从一个小的数据块。我正在寻找一种更有效的方法来做到这一点。

提前致谢。

这是我的存储过程。

CREATE TABLE #Customer_Temp (
  AccountCode varchar(50),
  AccountTitle varchar(50),
  CurrentBalance int,
  FirstBalance int,
  SecondBalance int,
  ThirdBalance int,
  FourthBalance int,
  FifthBalance int,
  SixthBalance int,
  SeventhBalance int,
  EighthBalance int,
  OpeningBalance int
)

INSERT INTO #customer_temp (AccountCode, AccountTitle, OpeningBalance)
  SELECT
    Customer.AccountCode,
    Customer.Name,
    COA.OpeningBalance
  FROM Customers AS Customer
  INNER JOIN ChartOfAccount AS COA
    ON COA.CompanyId = @Companyid
    AND COA.BusinessUnitId = @BusinessUnitId
    AND COA.ChartAccount = Customer.AccountCode

--Create Table And Duplicate Customers Data In it ENDED

DECLARE @DrAmount AS int
DECLARE @CrAmount AS int
DECLARE @Balance AS int
DECLARE @FBalance AS int
DECLARE @SBalance AS int
DECLARE @TBalance AS int
DECLARE @FoBalance AS int
DECLARE @FIBalance AS int
DECLARE @SIBalance AS int
DECLARE @SEBalance AS int
DECLARE @EBalance AS int

DECLARE @FSDate AS date
DECLARE @FLDate AS date
DECLARE @SSDate AS date
DECLARE @SLDate AS date
DECLARE @TSDate AS date
DECLARE @TLDate AS date
DECLARE @FOSDate AS date
DECLARE @FOLDate AS date
DECLARE @FISDate AS date
DECLARE @FILDate AS date
DECLARE @SISDate AS date
DECLARE @SILDate AS date
DECLARE @SESDate AS date
DECLARE @SELDate AS date
DECLARE @ESDate AS date

SET @FSDate = DATEADD(DAY, -1, @StartDate)
SET @FLDate = DATEADD(DAY, -6, @FSDate)

SET @SSDate = DATEADD(DAY, -1, @FLDate)
SET @SLDate = DATEADD(DAY, -6, @SSDate)

SET @TSDate = DATEADD(DAY, -1, @SLDate)
SET @TLDate = DATEADD(DAY, -14, @TSDate)

SET @FOSDate = DATEADD(DAY, -1, @TLDate)
SET @FOLDate = DATEADD(DAY, -14, @FOSDate)

SET @FISDate = DATEADD(DAY, -1, @FOLDate)
SET @FILDate = DATEADD(DAY, -14, @FISDate)

SET @SISDate = DATEADD(DAY, -1, @FILDate)
SET @SILDate = DATEADD(DAY, -29, @SISDate)

SET @SESDate = DATEADD(DAY, -1, @SILDate)
SET @SELDate = DATEADD(DAY, -89, @SESDate)

SET @ESDate = DATEADD(DAY, -1, @SELDate)

DECLARE @TempCCode AS varchar(50)
DECLARE @TempOBalance AS float

DECLARE CustomerCursor CURSOR FOR
SELECT
  AccountCode,
  OpeningBalance
FROM #Customer_Temp

OPEN CustomerCursor
FETCH NEXT FROM CustomerCursor INTO @TempCCode, @TempOBalance

WHILE @@FETCH_STATUS = 0
BEGIN
  EXEC @FBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                           @Companyid,
                                           @TempCCode,
                                           @FSDate,
                                           @FLDate,
                                           @Fyear

  EXEC @SBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                           @Companyid,
                                           @TempCCode,
                                           @SSDate,
                                           @SLDate,
                                           @Fyear

  EXEC @TBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                           @Companyid,
                                           @TempCCode,
                                           @TSDate,
                                           @TLDate,
                                           @Fyear

  EXEC @FoBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                            @Companyid,
                                            @TempCCode,
                                            @FOSDate,
                                            @FOLDate,
                                            @Fyear

  EXEC @FIBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                            @Companyid,
                                            @TempCCode,
                                            @FISDate,
                                            @FILDate,
                                            @Fyear

  EXEC @SIBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                            @Companyid,
                                            @TempCCode,
                                            @SISDate,
                                            @SILDate,
                                            @Fyear

  PRINT @SESDate
  PRINT @SELDate
  EXEC @SEBalance =
  GetBalanceOfAgingOnDate @BusinessUnitId,
                          @Companyid,
                          @TempCCode,
                          @SESDate,
                          @SELDate,
                          @Fyear

  EXEC @EBalance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                           @Companyid,
                                           @TempCCode,
                                           @ESDate,
                                           @EndDate,
                                           @Fyear

  EXEC @Balance = GetBalanceOfAgingOnDate @BusinessUnitId,
                                          @Companyid,
                                          @TempCCode,
                                          @StartDate,
                                          @EndDate,
                                          @Fyear
  UPDATE #Customer_Temp
  SET CurrentBalance = (@Balance + @TempOBalance),
      FirstBalance = @FBalance,
      SecondBalance = @SBalance,
      ThirdBalance = @TBalance,
      FourthBalance = @FoBalance,
      FifthBalance = @FIBalance,
      SixthBalance = @SIBalance,
      SeventhBalance = @SEBalance,
      EighthBalance = @EBalance
  WHERE AccountCode = @TempCCode
  FETCH NEXT FROM CustomerCursor INTO @TempCCode, @TempOBalance
END

CLOSE CustomerCursor
DEALLOCATE CustomerCursor

这里是游标中调用的存储过程

CREATE PROCEDURE [dbo].[GetBalanceOfAgingOnDate]
@BusinessUnitId int,
@Companyid int,
@ChartAccount as varchar (50),
@StartDate as DateTime,
@EndDate as DateTime,
@Fyear as varchar(50)
AS BEGIN

Declare @DrAmount as int
Declare @CrAmount as int
Declare @Balance as int

set @DrAmount=(select sum(Dr_Amount) from AccountVocherMaster AS AM ,
AccountVocherChild AS AC  Where AM.CompanyId = @Companyid AND
AM.BusinessUnitId = @BusinessUnitId  AND AM.FYear = @Fyear AND
AM.VocherId = AC.VocherId  AND  AC.AccountCode=@ChartAccount   AND
AC.CreatedOn Between  @EndDate AND @StartDate);

set @CrAmount=(select sum(Cr_Amount) from AccountVocherMaster AS AM ,
AccountVocherChild AS AC  Where AM.CompanyId = @Companyid AND
AM.BusinessUnitId = @BusinessUnitId  AND AM.FYear = @Fyear AND
AM.VocherId = AC.VocherId  AND  AC.AccountCode=@ChartAccount   AND
AC.CreatedOn Between  @EndDate AND @StartDate);

set @Balance = @DrAmount - @CrAmount ;

return ISNULL(@Balance,0)

END

【问题讨论】:

  • 嗯,很明显,去掉光标和程序。将其作为基于集合而不是基于行来处理。
  • 正如 JamesZ 所避免的那样,循环和游标通常很慢。此外,如果不知道里面的那些存储过程是如何编写的,你就不能排除它们成为瓶颈。也许您可以扩展您要完成的工作,如果没有该上下文,这可能是一个非常大的讨论,涉及逐个分解您的查询。
  • @Nicarus 我已经添加了有问题的调用存储过程,请帮助我问候
  • "!help me" 翻译为“我不知道如何编写 SQL,所以你告诉我如何让它更快(JamesZ 做到了),现在我希望你为我编写代码免费”?错误的网站。让我们结束这个 - 我们不是代码编写服务。
  • 我投票结束这个问题,因为我们不是代码编写服务。 OP 显然已经要求我们完全解决他的问题,准备好复制粘贴,而不是解释问题并教育他如何改进他的代码。

标签: sql sql-server performance stored-procedures


【解决方案1】:
  • 使游标成为局部变量DECLARE @CustomerCursor CURSOR,确保它不是动态的并且不反映对游标源表的更新SET @CustomerCursor = CURSOR FAST_FORWARD FOR,在按当前光标位置结束更新,避免额外搜索 UPDATE #Customer_Temp SET ... WHERE CURRENT OF @CustomerCursor
  • 通过单次选择获得相似条件的不同列的聚合选择@DrAmount = sum(Dr_Amount), @CrAmount = sum(Cr_Amount) 还有更多:选择 @Balance = sum(Dr_Amount) - sum(Cr_Amount)
  • 避免古式逗号连接,编写内连接或外连接,将连接条件放入ON子句,将过滤放入WHERE子句
  • 避免将 SQL 视为 PHP 或 Pascal 等常规编程语言;它是面向结果集的;尝试制作 INLINE TABLE FUNCTION 而不是过程 (GetBalanceOfAgingOnDate) - 这将使您能够从查询中加入; 提示:不要将其设为标量值或表值函数。查看您的聚合 sp 以及它是如何使用的。对于每一行(公司),您一次又一次地调用该 sp,并在其中聚合一些值。为什么不按公司和 BusinessUnitID 进行分组,从 AccountVocherMaster 运行单个选择并将其加入您的#Customer_Temp?为什么不通过单个查询汇总不同时期的摘要?再看一遍:你有一个公司列表,一个聚合查询,提供了一些按公司分组的汇总值......为什么你还有一个游标?这是一个单一的查询作业
  • 如果您有大量数据,并且按日期过滤是使其快速运行的唯一方法 - 不要进行一次查询,而是进行 10 次查询。无论如何,这将比 10*2*[N 个公司] 实际需要的时间段要好得多 - 最大范围是 [@StartDate-180, @StartDate] - 看起来单个查询又是一个好主意。在某些情况下,如果您的数据非常大,游标和按标量值过滤可能仍然是提高性能的一个很好的更改,因此您可能仍希望按单个 CompanyID 过滤 - 好的,但您聚合的数据仍然位于在相同的两个表中的固定日期范围内 - AccountVocherMaster 和 AccountVocherChild;遍历公司,在光标内运行单个聚合查询
  • 在您的 GetBalanceOfAgingOnDate 的源中绝对没有什么聪明之处 - 不知道为什么要将它作为一个单独的模块保留
  • 周围一定有交易

【讨论】:

    【解决方案2】:

    您的问题是您不思考 SQL 并尝试变得聪明 - 您编写过程代码,SQL Server 必须一个一个地执行这些代码,而不是在集合中工作并让查询优化器找出如何最有效地做到这一点.

    游标和过程通常是缓慢的元素。您最好在尽可能少的原子 SQL 语句中对此进行表述,即使这些语句有一两页长。然后查询优化器可以找出如何最有效地实现这个结果。

    在您的情况下,第二个 SP 在光标中被重复调用 - 有可能更好地实现这一点。一次或多次通话。

    【讨论】:

      【解决方案3】:

      希望这可以让您走上正轨。你的问题有很多,所以我可能无法(a)理解这里的所有内容并(b)解决每个部分,但希望你能从这里得到它。我认为不需要任何程序或循环。试试这个,我已经评论了一些上下文:

      /* Construct a temp table to store all balance dates (and respective names) */
      CREATE TABLE #BalanceDates (BalanceName VARCHAR(20), StartDate DATETIME, EndDate DATETIME);
      
      INSERT INTO BalanceDates VALUES ('FirstBalance',DATEADD(DAY,-1,@StartDate),DATEADD(DAY,-6,@StartDate));
      -- And so on with remaining inserts...
      
      /* Create a denormalized table of all balances */
      SELECT
          [FirstBalance],
          [SecondBalance],
          ...
      INTO
          #Balances
      FROM
      (
      SELECT
          BD.BalanceName,
          ISNULL(SUM(Dr_Amount) - SUM(Cr_Amount),0) AS BalanceAmount
      FROM
          #BalanceDates BD
      LEFT JOIN
          AccountVocherChild AC
          ON  (AC.CreatedOn BETWEEN BD.StartDate AND BD.EndDate)
      LEFT JOIN
          AccountVocherMaster AM
          ON  (AM.VocherId = AC.VocherId)
      WHERE
          AM.CompanyId = @Companyid AND
          AM.BusinessUnitId = @BusinessUnitId AND
          AM.FYear = @Fyear AND
          AC.AccountCode = @ChartAccount
      GROUP BY
          BD.BalanceName
      ) data
      PIVOT
      (
      AVG(BalanceAmount)
      FOR BalanceName IN ([FirstBalance],[SecondBalance],...)
      ) pvt;
      
      /* Update the table accordingly */
      UPDATE tgt
      SET
          FirstBalance = src.FirstBalance,
          SecondBalance = src.SecondBalance,
          ...
      FROM #Customer_Temp tgt
      JOIN #Balances src
      ON (1 = 1);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-30
        • 1970-01-01
        • 2011-03-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多