【问题标题】:Improve INSERT INTO SELECT Query performance?提高 INSERT INTO SELECT 查询性能?
【发布时间】:2020-02-08 07:48:37
【问题描述】:

我正在尝试改进旧的 while 循环查询。到目前为止尝试了这个,但它仍然很慢。不确定可能是 SUM 执行缓慢。

旧查询(10 分钟以上)

ALTER PROCEDURE [dbo].[process_tax]
   @userid VARCHAR(10),
   @remark NVARCHAR(500),
   @tdate DATE,
   @roadno NVARCHAR(10),
   @inst INT
AS
BEGIN 
    IF OBJECT_ID('tempdb..#tempProcess_tax_1') IS NOT NULL  
        DROP TABLE #tempProcess_tax_1

    CREATE TABLE #tempProcess_tax_1 
    (
         RowID INT IDENTITY(1, 1), 
         clid_ INT,
         hlid_ INT,
         holdinNo_ NVARCHAR(500),
         holding_ NVARCHAR(50),
         clientid_ NVARCHAR(500),
         clientName_ NVARCHAR(500)
    )

    INSERT INTO #tempProcess_tax_1 (clid_, hlid_, holdinNo_, holding_, clientid_, clientName_)
        SELECT
            cl.clid AS clid_, cl.id AS hlid_, holdinNo, holding,
            ClientID AS clientid_, ClientName AS clientName_  
        FROM
            tx_holding AS cl 
        WHERE
            cl.status = 1 AND cl.roadno = @roadno
            AND cl.id IN (SELECT hlid FROM tx_asset WHERE asset IS NOT NULL) 
            AND cl.clid IN (SELECT id FROM tbl_client WHERE client_type = 'Non-Govt.') 
            AND cl.id NOT IN (SELECT hlid FROM tx_bill_pay 
                              WHERE YEAR(date_month) = YEAR(@tdate) 
                                 AND hlid IS NOT NULL 
                              GROUP BY hlid)

    DECLARE @NumberRecords_1 INT, @RowCounter_1 INT

    SET @NumberRecords_1 = (SELECT COUNT(*) FROM #tempProcess_tax_1)
    SET @RowCounter_1 = 1

    WHILE @RowCounter_1 <= @NumberRecords_1
    BEGIN
        DECLARE @clid_ INT
        DECLARE @hlid_ INT
        DECLARE @holdinNo_ NVARCHAR(50)
        DECLARE @holding_ NVARCHAR(50)
        DECLARE @clientid_ NVARCHAR(100)
        DECLARE @clientName_ NVARCHAR(250)
        DECLARE @bill AS MONEY
        DECLARE @sr AS MONEY;

   SELECT  
       @clid_ = clid_,
       @hlid_ = hlid_,
       @holdinNo_ = holdinNo_, @holding_ = holding_,
       @clientid_ = clientid_, @clientName_ = clientName_
   FROM 
       #tempProcess_tax_1 
   WHERE 
       RowID = @RowCounter_1

SET @bill = (SELECT 
                 CASE WHEN SUM(netvalue) IS NULL
                         THEN 0 
                         ELSE SUM(netvalue) 
                 END  
             FROM 
                 tx_bill 
             WHERE 
                 hlid = @hlid_ 
                 AND itemID NOT IN (8, 6) 
                 AND YEAR(date_month) = YEAR(@tdate))
    SET @sr = (SELECT
                   CASE WHEN SUM(asset * rate / 100) IS NULL
                           THEN 0 
                           ELSE SUM(asset * rate / 100) 
                   END
               FROM 
                   tx_bill 
               WHERE
                   hlid = @hlid_ 
                   AND itemID = 6 
                   AND YEAR(date_month) = YEAR(@tdate))

    INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date) 
    VALUES (@clid_, @hlid_, @sr, @bill / 4, DATEADD(day, -1, DATEADD(m, 3, @tdate)), CONCAT(@holdinNo_, YEAR(@tdate), '1'), @holdinNo_, @holding_, @clientid_, @clientName_, CONCAT(@holdinNo_, YEAR@tdate)), @tdate, @bill, 1, @userid, GETDATE())

    INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date) 
    VALUES (@clid_, @hlid_, 0, 2 * (@bill / 4), DATEADD(day, -1, DATEADD(m, 6, @tdate)), CONCAT(@holdinNo_, YEAR(@tdate), '2'), @holdinNo_, @holding_, @clientid_, @clientName_, CONCAT(@holdinNo_, YEAR(@tdate)), @tdate, @bill, 2, @userid, GETDATE())

    SET @RowCounter_1 = @RowCounter_1 + 1
END

DROP TABLE #tempProcess_tax_1
END

新查询(1-2 分钟)

ALTER PROCEDURE [dbo].[process_tax]
   @userid varchar(10),
   @remark nvarchar(500),
   @tdate date ,
   @roadno nvarchar(10),
   @inst int
   as
BEGIN 

 insert  into tx_bill_pay(
          clid,
          hlid,
          swercharge,
          pay_bill,
          pdate,
          bill_id,
          holdingNo,
          holding,
          ClientID, 
          ClientName,
          billno, 
          date_month, 
          bill,
          install ,
          inserted_by, 
          inserted_date)

     select 
           cl.clid,
           cl.id,
     swercharge=(select  case when sum(asset*rate/100) is null then 0 else 
               sum(asset*rate/100) end  from tx_bill where hlid=cl.id and 
               itemID =6 and year(date_month)=YEAR(@tdate)),
     pay_bill=(select  case when sum(netvalue) is null then 0 else 
             sum(netvalue) end  from tx_bill where hlid=cl.id and itemID not 
             in(8,6) and year(date_month)=YEAR(@tdate))/4,  
    DATEADD(day,-1,
    DATEADD(m,3,@tdate)),
    CONCAT(cl.holdinNo, year(@tdate),'1'),
    cl.holdinNo,
    cl.holding,
    cl.ClientID, 
    cl.clientName, 
    CONCAT(cl.holdinNo, 
    year(@tdate)), 
    @tdate,
    bill=(select  case when sum(netvalue) is null then 0 else sum(netvalue) 
         end  from tx_bill where hlid=cl.id and itemID not in(8,6) and 
         year(date_month)=YEAR(@tdate))/4,
    1, 
    @userid, getdate()  
    from  
    (select * 
    from tx_holding as cl 
    where cl.status=1 and cl.roadno=@roadno) AS cl
    INNER JOIN (
     select DISTINCT hlid from tx_asset where asset is not null
     ) AS A 
    ON Cl.id = A.hlid 
     INNER JOIN (
      select DISTINCT  id from tbl_client where client_type='Non-Govt.'
     ) AS C 
    ON   cl.clid=C.id
    WHERE    NOT EXISTS
        (   SELECT  1
            FROM    tx_bill_pay as bp
            WHERE    year(date_month)=year(@tdate)
            and bp.hlid=cl.id
        )   


 insert  into tx_bill_pay(clid,hlid 
    ,swercharge,pay_bill,pdate,bill_id,holdingNo,holding,ClientID, 
    ClientName, billno, date_month, bill, install ,inserted_by, 
    inserted_date)
select 
    cl.clid,
    cl.id,
    0,
    pay_bill=2*((select  case when sum(netvalue) is null then 0 else sum(netvalue) end  from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(@tdate))/4),
    DATEADD(day,-1,
    DATEADD(m,3,@tdate)),
    CONCAT(cl.holdinNo, year(@tdate),'2'),
    cl.holdinNo,
    cl.holding,
    cl.ClientID, 
    cl.clientName, 
    CONCAT(cl.holdinNo, year(@tdate)) , 
    @tdate,
    bill=(select  case when sum(netvalue) is null then 0 else sum(netvalue) 
         end  from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(@tdate))/4,
    2, 
    @userid, getdate()  
from  
    (select * 
    from tx_holding as cl 
    where cl.status=1 and cl.roadno=@roadno) AS cl
    INNER JOIN (
     select DISTINCT hlid from tx_asset where asset is not null
     ) AS A 
    ON Cl.id = A.hlid 
     INNER JOIN (
      select DISTINCT  id from tbl_client where client_type='Non-Govt.'
     ) AS C 
    ON   cl.clid=C.id
    WHERE    cl.id not in
        (   SELECT  hlid
            FROM    tx_bill_pay
            WHERE  year(date_month)=year(@tdate)
            and hlid is not null group by hlid
        )

【问题讨论】:

  • 摆脱循环的出色工作。但如前所述,没有更多详细信息,没有人可以在这里为您提供帮助。从执行计划开始。
  • @DaleBurrell 我尝试 pastetheplan 但它显示错误“提供的 XML 未正确解析。您确定您有有效的查询计划 XML 文本吗?”
  • 嗯....你粘贴了执行计划中的 xml 吗?
  • @SeanLange 感谢您的评论和链接。它可以帮助我找出问题。

标签: sql sql-server performance tsql


【解决方案1】:

删除循环做得很好!

我会指出一个可能的性能问题,特别是year(date_month)=year(@tdate)

因为一个列被包裹在一个函数中,所以它是non-SARGABLE。这意味着无法直接评估 date_month 值,因此无法使用该列的索引和统计信息。

为了解决这个问题,我建议进行以下更改:

在 SP 的顶部再定义两个变量:

DECLARE @YearStart AS DATETIME, @NextYearStart DATETIME
SET @YearStart = DATEADD(yy, DATEDIFF(yy, 0, @tdate ), 0 )
SET @NextYearStart = DATEADD( yy, @YearStart, 1 )

然后将year(date_month)=year(@tdate)的所有实例替换为

@YearStart <= date_month AND date_month < @NextYearStart

上面的表达式查找大于或等于一年中第一天午夜(注意时间分量)且小于下一年午夜的 date_month 值。

接下来,我将查看查询计划,看看 SQL Server 是否提供了"Missing Index" recommendation(如果确实建议索引,它应该出现在计划图的正上方)。尝试添加推荐的缺失索引,然后检查您是否获得了性能改进。如果您没有得到改进,请删除索引 - 有时建议无济于事。

更新

有 3 个子查询(填充列 swerchargepay_billbill)使用具有不同 WHERE 条件的 tx_bill 表将导致每次主查询执行至少 3 次评估此表。

根据表的大小,一次计算所有 3 个子查询并将结果保存到临时表(或表变量)可能会更有效,如下所示:

SELECT hlid,
    ISNULL( SUM( CASE WHEN itemID NOT IN (8, 6) THEN netvalue END ), 0 ) AS Bill,
    ISNULL( SUM( CASE WHEN itemID = 6 THEN asset * rate / 100 END ), 0 ) AS Sr
INTO #Bills
FROM tx_bill 
WHERE @YearStart <= date_month AND date_month < @NextYearStart
    AND NOT hlid IS NULL
GROUP BY hlid

在您的主要查询中,按如下方式加入该表:

LEFT JOIN #SumBills AS SumBills ON SumBills.hlid = cl.hlid

并在SELECT 中按照以下示例更改分配:

pay_bill= ISNULL( SumBills.Bill, 0 ) / 4,

注意:我相信您在bill 列中存在错误,因为该值除以4,它不在原始光标中。

最后一点:

正如@GarethD 在his answer 对您的previous question 中所说,正确格式化您的代码可以显着减少理解和更改代码所需的时间。我会更进一步说代码格式代表了你对手头问题的态度和理解。

【讨论】:

  • 恕我直言,这只是冰山一角。在开始查看索引之前,似乎还有很大的改进空间。
  • @SeanLange,我同意,但是上面的构造出现在他的 SP 的几个地方,并且是 5 分钟修复。我没有时间将 move tx_bill 子查询写成单独的查询答案:)
  • @Alex 感谢您的辛勤工作和时间。我会检查你的提示。我添加非聚集索引,然后我的查询需要 5 秒才能运行。
【解决方案2】:

我添加了非聚集索引,然后我的查询需要 5 秒才能运行。这几乎解决了我的问题。 @Alex 感谢您的辛勤工作和时间。我会检查你的提示。我对你的评论投赞成票

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    • 2018-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多