存储过程的性能问题

时间:2016-02-13 07:17:25

标签: sql sql-server performance stored-procedures

我有一个使用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

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

你的问题是你不认为SQL并试图变得聪明 - 你编写过程代码,SQL Server必须逐个执行INSTEAD才能在set中工作并让查询优化器弄清楚如何最有效地执行

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

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

答案 2 :(得分:0)

希望这可以让你走上正轨。你的问题有很多,所以我可能不会(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);