【问题标题】:Speed issues with While loops in SQL stored proceduresSQL 存储过程中的 While 循环的速度问题
【发布时间】:2012-09-23 22:47:42
【问题描述】:

我正在接管一些尝试同时存储 ID 列表的代码,我发现对于我们尝试完成的操作,该代码的运行速度非常慢。另外,在某些情况下,由于大量 id 会导致死锁。

USE [store]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[UpdateImagePriority]

   @separator CHAR(1),
   @filename  varchar(50),
   @parentId   int,
   @slaveIds varchar(8000)

   AS
   BEGIN      
      SET NOCOUNT ON
      DECLARE @SLAPriorityint
      DECLARE @separator_position INT 
      DECLARE @array_value VARCHAR(50)

      SET @slaveIds = @slaveIds + @separator
      SET @SLAPriority= 0

      WHILE PATINDEX('%' + @separator + '%', @slaveIds ) <> 0
           BEGIN

               SET @SLAPriority= @SLAPriority+ 1

               SELECT  @separator_position = PATINDEX('%' + @separator + '%',@slaveIds )
               SELECT  @array_value = LEFT(@slaveIds , @separator_position - 1)

               SELECT  Array_Value = @array_value

               SELECT  @slaveIds = STUFF(@slaveIds , 1, @separator_position, '')

               UPDATE image_info
               SET SLA_PRIORITY = @SLAPriority
               WHERE FILE=@filename and EXT_PAR_ID=@parentId   and SLA_ID=@array_value
           END
      SET NOCOUNT OFF
   END

这是我们将传入的示例:

例如

separator = ','
filename = 'burgerking'
parentId = '1859'
slaveIds = '15,16,19,20,21,25,28,29,30,38,99'

关于如何提高此代码速度的任何建议。

提前致谢!

【问题讨论】:

    标签: sql stored-procedures while-loop


    【解决方案1】:

    您正在寻找的是一个表值函数,用于将您的值拆分为一个表。那么你只需要一个UPDATE .. FROM .. JOIN 语句。

    CREATE PROCEDURE [dbo].[UpdateImagePriority]
       @separator CHAR(1),
       @filename  varchar(50),
       @parentId   int,
       @slaveIds varchar(8000)
    AS
    
    set @slaveIds = @slaveIds + @separator
    ;WITH SplitString AS
    (
        SELECT
            1 ID,LEFT(@slaveIds,CHARINDEX(',',@slaveIds)-1) AS Part,RIGHT(@slaveIds,LEN(@slaveIds)-CHARINDEX(',',@slaveIds)) AS Remainder
        UNION ALL
        SELECT
            ID+1,LEFT(Remainder,CHARINDEX(',',Remainder)-1),RIGHT(Remainder,LEN(Remainder)-CHARINDEX(',',Remainder))
            FROM SplitString
            WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)>0
    )
    update i
    SET SLA_PRIORITY = s.ID
    from splitstring s
    join image_info i on i.[FILE]=@filename and i.EXT_PAR_ID=@parentId and i.SLA_ID= s.Part
    where s.Part > ''
    

    对于 SQL Server 2000,或者只是为了使字符串拆分可重复使用,我从另一个问题中提取了 this function

    create function dbo.SplitString 
        (
            @str varchar(8000), 
            @separator char(1)
        )
        returns table
        AS
        return (
            with tokens(p, a, b) AS (
                select 
                    1, 
                    1, 
                    charindex(@separator, @str)
                union all
                select
                    p + 1, 
                    b + 1, 
                    charindex(@separator, @str, b + 1)
                from tokens
                where b > 0
            )
            select
                p Id,
                substring(
                    @str, 
                    a, 
                    case when b > 0 then b-a ELSE 8000 end) 
                AS Part
            from tokens
          )
    GO
    

    那么你的SP就变成了

    CREATE PROCEDURE [dbo].[UpdateImagePriority]
       @separator CHAR(1),
       @filename  varchar(50),
       @parentId   int,
       @slaveIds varchar(8000)
    AS
    
    update i
    SET SLA_PRIORITY = s.ID
    from dbo.splitstring(@slaveIds,@separator) s
    join image_info i on i.[FILE]=@filename and i.EXT_PAR_ID=@parentId and i.SLA_ID= s.Part
    where s.Part > ''
    GO
    

    【讨论】:

    • 很好地使用了 CTE 查询来分隔逗号字符串。但是在函数中会更好,所以它是可重用的......
    • 你是对的。我一直在想no-trace 查询,但如果他正在创建一个过程,他可以同时创建一个函数。
    • 嘿,理查德,这在我的本地数据库中运行良好,但是当我尝试在实时数据库上使用它时,它返回错误:“Msg 156, Level 15, State 1, Procedure UpdateImagePriority, Line 15关键字“WITH”附近的语法不正确。”。我假设它运行的 SQL Server 版本(8.0.2055)有问题?
    • *运行 SQL 2000 的更多信息,我认为“with”不在受支持的列表中。
    【解决方案2】:

    使用函数将数组转换为 XML 文档,然后很容易将其作为整数的单列表返回:

    use tempdb
    go
    
    create function dbo.ParseIDs(@ids varchar(max), @separator char(1)) returns @rtn table (
      id int
    )
    as
    begin
      declare @xml xml
    
      set @xml = '<root><n>' + replace(@ids, @separator, '</n><n>') + '</n></root>'
    
      insert into @rtn
      select id.value('.', 'int')as id
      from @xml.nodes('/root/n') as records(id)
    
      return
    end
    go
    
    declare @buf varchar(max) = '15,16,19,20,21,25,28,29,30,38,99'
    select * from dbo.ParseIDs(@buf, ',')
    go
    
    drop function dbo.ParseIDs
    go
    

    这将返回以下内容:

    id
    ----
    15
    16
    19
    20
    21
    25
    28
    29
    30
    38
    99
    

    这样做很容易:

    UPDATE image_info
    SET SLA_PRIORITY = @SLAPriority
    WHERE FILE=@filename and EXT_PAR_ID=@parentId   and SLA_ID in (
      select id from dbo.ParseIDs(@array_value, ',')
    )
    

    这可能会更好:

    UPDATE tbl
    SET    tbl.SLA_PRIORITY = @SLAPriority
    FROM   dbo.ParseIDs(@array_value, ',') as x
           inner join image_info tbl on x.id = tbl.SLA_ID
    WHERE  tbl.FILE=@filename and tbl.EXT_PAR_ID=@parentId
    

    【讨论】:

      【解决方案3】:

      进行基于集合的更新:

          USE [store]
          GO
          SET ANSI_NULLS ON
          GO
          SET QUOTED_IDENTIFIER ON
          GO
          ALTER PROCEDURE [dbo].[UpdateImagePriority]
      
         @separator CHAR(1),
         @filename  varchar(50),
         @parentId   int,
         @slaveIds varchar(8000)
      
         AS
         BEGIN      
            SET NOCOUNT ON
            DECLARE @SLAPriorityint
            DECLARE @separator_position INT 
            DECLARE @array_value VARCHAR(50)
      
            SET @slaveIds = @slaveIds + @separator
            SET @SLAPriority= 0
      
            DECLARE @slaveIdTable Table
            (
              ids INT,
              SlaPriority INT
            )
            WHILE PATINDEX('%' + @separator + '%', @slaveIds ) <> 0
                 BEGIN
      
                     SET @SLAPriority= @SLAPriority+ 1
      
                     SELECT  @separator_position = PATINDEX('%' + @separator + '%',@slaveIds )
                     SELECT  @array_value = LEFT(@slaveIds , @separator_position - 1)
      
                     -- get table of ids
                     INSERT INTO @slaveIdTable (ids,SlaPriority) VALUES(@array_value,@SLAPriority);
      
                     SELECT  @slaveIds = STUFF(@slaveIds , 1, @separator_position, '')
      
                 END
      
          UPDATE ii
              SLA_PRIORITY = @SLAPriority
          FROM image_info ii
          JOIN @slaveIdTable st ON st.ids = st.SLA_ID
          WHERE
              st.[FILE] =  @filename AND
              st.EXT_PAR_ID = @parentId                   
      
            SET NOCOUNT OFF
         END
      

      Richard 的回答更优雅一些。在我工作的时候没有看到它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多