重负载下的SQL Server检索错误(200个并发用户)

时间:2012-02-07 13:57:47

标签: sql sql-server tsql sql-server-2008-r2

目前我在SQL Server检索过程中遇到了一个问题。

当同一参数同时由多个用户(用户数> 40)执行时,存储过程返回意外结果。对于No of users< = 20,它显示预期结果。使用jMeter测量的错误率为0.07-0.10%。

以下SQL语句应返回一行。但是在负载很重的情况下,它有时不返回任何行。

exec RetrievingNode @Keys='one,two,three'

这是存储过程。

CREATE PROCEDURE [dbo].[RetrievingNode] @Keys nvarchar(max) 
as 
  Begin 
      SET NOCOUNT ON; 

      --*************************************** 
      --*************************************** 
      -- Turn On or Off updation of LastAccessDateTime  
      -- To Turn on set @TurnOnUpdation=1 
      -- To Turn Off set @TurnOnUpdation=0 
      declare @TurnOnUpdation as bit 

      set @TurnOnUpdation=1 

      --*************************************** 
      --*************************************** 
      declare @Variable as table( 
        Value         nvarchar(max), 
        KeyedNodes_Id int, 
        KeyNodeValue  nvarchar(max), 
        IsParent      bit, 
        IsReadOnly    bit ) 
      -- this table will hold ids against which LastAccessDateTime can be updated.       
      declare @tbl_Keys table( 
        id    int identity(1, 1), 
        Value nvarchar(max)) 

      --following will take all the keys into @tbl_Keys 
      insert into @tbl_Keys 
                  (Value) 
      select Data 
      from   Split(@Keys, ',') 

      DECLARE @count as int 
      DECLARE @counter as int 

      SET @counter=1 

      SELECT @count = COUNT(*) 
      FROM   @tbl_Keys 

      --Declare @ChildId as int 
      declare @ChildNodes table( 
        id            int, 
        Value         nvarchar(max), 
        ParentNode_id int) 
      declare @ParentId as int 

      WHILE( @counter <= @count ) 
        BEGIN 
            DECLARE @Key as nvarchar(max) 

            SELECT @Key = Value 
            FROM   @tbl_Keys 
            WHERE  id = @counter 

            if( @counter = 1 ) 
              begin 
                  if not exists(select * 
                                from   keyednodes(nolock) KN 
                                where  KN.Value = @Key 
                                       and KN.ParentNode_Id is null) 
                    BEGIN 
                        ---node does not exists 
                        Break; 
                    END 

                  declare @ValueExistsForFirstKey as bit 

                  set @ValueExistsForFirstKey=0 

                  insert into @ChildNodes 
                  select k2.Id, 
                         k2.Value, 
                         k2.ParentNode_Id 
                  from   keyednodes(nolock) k1 
                         inner join keyednodes(nolock) k2 
                           on k1.id = k2.ParentNode_id 
                  where  K1.value = @Key 
                         and k1.ParentNode_Id is null 

                  if( @count = 1 ) 
                    BEGIN 
                        IF EXISTS(select GV.Value, 
                                         GV.KeyedNodes_Id, 
                                         KN.Value 
                                  from   keyednodes(nolock) KN 
                                         inner join GlobalVariables(nolock) GV 
                                           on KN.Id = GV.KeyedNodes_Id 
                                              and KN.Value = @Key 
                                              and KN.ParentNode_Id is null) 
                          BEGIN -- If value of node exists 
                              SET @ValueExistsForFirstKey=1 

                              insert into @Variable 
                              select GV.Value, 
                                     GV.KeyedNodes_Id, 
                                     KN.Value, 
                                     1, 
                                     GV.ReadOnly 
                              from   keyednodes(nolock) KN 
                                     inner join GlobalVariables(nolock) GV 
                                       on KN.Id = GV.KeyedNodes_Id 
                                          and KN.Value = @Key 
                                          and KN.ParentNode_Id is null 
                          END 
                        ELSE 
                          IF NOT EXISTS(select * 
                                        from   @ChildNodes) 
                            BEGIN 
                                insert into @Variable 
                                select -1, 
                                       -1, 
                                       -1, 
                                       0, 
                                       0 
                                --Node exists but No value or children exists 

                                GOTO ExitFromProcedure 
                            END 

                        IF( @counter = @count ) 
                          BEGIN 
                              if exists(select Id, 
                                               Value, 
                                               ParentNode_Id 
                                        from   keyednodes(nolock) KN 
                                        where  KN.Value = @Key 
                                               and KN.ParentNode_Id is null) 
                                BEGIN 
                                    if( @ValueExistsForFirstKey = 0 ) 
                                      and ( not exists(select * 
                                                       from   @ChildNodes) ) 
                                      BEGIN 
                                          insert into @Variable 
                                          select -1, 
                                                 -1, 
                                                 -1, 
                                                 0, 
                                                 0 

                                          GOTO ExitFromProcedure 
                                      END 
                                END 
                          END 
                    END 
              end 
            else 
              begin 
                  declare @KeyIsChild as int 

                  set @KeyIsChild=0 

                  if exists(select * 
                            from   @ChildNodes 
                            where  Value = @Key) 
                    begin 
                        set @KeyIsChild=1 
                    End 
                  ELSE 
                    BEGIN 
                        --Key path does not exist 
                        --print 'key path does not exist' 
                        GOTO ExitFromProcedure 
                    ENd 

                  if not exists(select * 
                                from   @ChildNodes 
                                where  Value = @Key) 
                    BEGIN 
                        set @ParentId=0 
                    END 
                  ELSE 
                    BEGIN 
                        select @ParentId = Id 
                        from   @ChildNodes 
                        where  Value = @Key 
                    ENd 

                  delete @ChildNodes 

                  if( @KeyIsChild = 1 ) 
                    begin 
                        insert into @ChildNodes 
                        select k2.Id, 
                               k2.Value, 
                               k2.ParentNode_Id 
                        from   keyednodes(nolock) k1 
                               inner join keyednodes(nolock) k2 
                                 on k1.id = k2.ParentNode_id 
                        where  k2.ParentNode_Id = @ParentId 
                    end 
              end 

            SET @counter=@counter + 1 
        END 

      if exists(select * 
                from   @ChildNodes) 
        begin 
            IF EXISTS (select GV.Value, 
                              CN.id, 
                              CN.Value 
                       from   @ChildNodes CN 
                              left outer join GlobalVariables(nolock) GV 
                                on CN.Id = GV.KeyedNodes_Id --children 
                       union all 
                       select GV.Value, 
                              GV.KeyedNodes_Id, 
                              KN.Value 
                       from   keyednodes(nolock) KN 
                              inner join GlobalVariables(nolock) GV 
                                on KN.Id = GV.KeyedNodes_Id 
                                   and KN.Id = @ParentId--children 
                      ) 
              BEGIN 
                  insert into @Variable 
                  select GV.Value, 
                         CN.id, 
                         CN.Value, 
                         0, 
                         GV.ReadOnly 
                  from   @ChildNodes CN 
                         left outer join GlobalVariables(nolock) GV 
                           on CN.Id = GV.KeyedNodes_Id --children 
                  union all 
                  select GV.Value, 
                         GV.KeyedNodes_Id, 
                         KN.Value, 
                         1, 
                         GV.ReadOnly 
                  from   keyednodes(nolock) KN 
                         inner join GlobalVariables(nolock) GV 
                           on KN.Id = GV.KeyedNodes_Id 
                              and KN.Id = @ParentId--children 
              END 
            ELSE 
              BEGIN 
                  if( @count <> 1 ) 
                    insert into @Variable 
                    select -1, 
                           -1, 
                           -1, 
                           0, 
                           0 

                  if( @count = 1 ) 
                    and not exists(select * 
                                   from   @Variable) 
                    insert into @Variable 
                    select -1, 
                           -1, 
                           -1, 
                           0, 
                           0 
              END 
        end 
      else 
        Begin 
            if ( @KeyIsChild = 1 ) 
              BEGIN 
                  IF EXISTS (select GV.Value, 
                                    GV.KeyedNodes_Id, 
                                    KN.Value 
                             from   keyednodes(nolock) KN 
                                    inner join GlobalVariables(nolock) GV 
                                      on KN.Id = GV.KeyedNodes_Id 
                                         and KN.Id = @ParentId) 
                    BEGIN 
                        ---IF value of the node exists 
                        insert into @Variable 
                        select GV.Value, 
                               GV.KeyedNodes_Id, 
                               KN.Value, 
                               1, 
                               GV.ReadOnly 
                        from   keyednodes(nolock) KN 
                               inner join GlobalVariables(nolock) GV 
                                 on KN.Id = GV.KeyedNodes_Id 
                                    and KN.Id = @ParentId --node 
                    END 
                  ELSE 
                    BEGIN 
                        insert into @Variable 
                        select -1, 
                               -1, 
                               -1, 
                               0, 
                               0 --Node exists but No value or children exists 
                    END 
              END 
        End 

      ---- 
      ExitFromProcedure: 

      select KeyedNodes_Id, 
             KeyNodeValue, 
             Value, 
             IsParent, 
             Isnull(IsReadOnly, 0)as IsReadOnly 
      from   @Variable 
      order  by IsParent desc, 
                KeyNodeValue asc 

      --update LastAccessDateTime 
      IF( @TurnOnUpdation = 1 ) 
        BEGIN 
            IF EXISTS(select * 
                      from   @Variable) 
              BEGIN 
                  UPDATE GlobalVariables 
                  SET    LastAccessDateTime = getdate() 
                  WHERE  KeyedNodes_Id IN (SELECT KeyedNodes_Id 
                                           FROM   @Variable) 
              END 
        END 
  End 

以下是上述过程中引用的Split()UDF:

CREATE FUNCTION [dbo].[Split] ( @RowData nvarchar(max), @SplitOn nvarchar(5) )
 RETURNS @RtnValue table 
 ( 
    Id int identity(1,1), 
    Data nvarchar(max) 
 ) AS 
BEGIN
 Declare @Cnt int; Set @Cnt = 1 

 While (Charindex(@SplitOn,@RowData)>0) 
 Begin 
    Insert Into @RtnValue (data) 
    Select Data = ltrim(rtrim(Substring(@RowData,1,Charindex(@SplitOn,@RowData)-1))) 
    Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData)) 
    Set @Cnt = @Cnt + 1 
 End 

 Insert Into @RtnValue (data) 
 Select Data = ltrim(rtrim(@RowData)) 

 Return 
END

3 个答案:

答案 0 :(得分:4)

这几乎肯定是事务隔离级别问题。我没有花时间真正深入研究proc的逻辑,但只是快速浏览一下,似乎这个过程更新状态被其他连接弄脏,导致间歇性的数据问题。

要尝试的东西(在您的TEST数据库上!)是删除所有NOLOCK提示,并在事务中执行此过程。就像我说的,我还没有真正分析过逻辑,但试试这个:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; --Increase isolation level
BEGIN TRAN
... your proc ...
COMMIT
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;  --Set back to the default on this connection

您的吞吐量可能会下降,但结果会变得正确。仔细分析proc以找到正确的事务隔离级别。可能SET TRANSACTION ISOLATION LEVEL READ COMMITTED SNAPSHOT将以更高的吞吐量返回正确的答案,但这是首先要分析和测试的东西。

答案 1 :(得分:3)

首先,IMO David Markle的回答是正确的。但是,FWIW我发布这个答案,为您提供清理,更正(恕我直言)的代码示例,其中删除了所有“(nolock)”提示。

我还没有实现Isolation和Transaction容器语句,但是如果请求我可以。我通常建议使用SNAPSHOT Isolation,因为存在高并发性问题。

下面的代码删除了许多冗余和过时的路径。以下经常使用的(反)模式:

IF  EXISTS(
        SELECT ...{complex query}
          )
begin
        INSERT ... SELECT ...{same complex query}
end
ELSE
begin
    {do something else}
end

已经变得更简单,更快:

INSERT ... SELECT ...{same complex query}

IF  @@ROWCOUNT = 0
begin
    {do something else}
end

最后,我当然不能测试代码或确保代码中没有错误。因此,如果您使用它,您应该确保自己彻底测试它。

CREATE PROCEDURE [dbo].[RetrievingNode] @Keys nvarchar(max) as
Begin
    SET NOCOUNT ON;
    -- Turn On or Off updation of LastAccessDateTime 
    DECLARE @TurnOnUpdation as bit;    SET @TurnOnUpdation=1

    DECLARE @Variable as table
        (
            Value nvarchar(max),
            KeyedNodes_Id int,
            KeyNodeValue nvarchar(max),
            IsParent bit, 
            IsReadOnly bit 
         )-- this table will hold ids against which LastAccessDateTime can be updated. 
    DECLARE @tbl_Keys table
        (
            id int identity(1,1),
            Value nvarchar(max)
        )

    --following will take all the keys into @tbl_Keys
    INSERT INTO @tbl_Keys(Value) SELECT Data FROM Split(@Keys,',');

    DECLARE @key_count as int;  SELECT  @key_count=COUNT(*) FROM @tbl_Keys
    DECLARE @key_depth as int;  

    DECLARE @ChildNodes table
        (
            id int,
            Value nvarchar(max),
            ParentNode_id int
        )
    DECLARE @ParentId as int
    DECLARE @KeyIsChild as int;     SET @KeyIsChild=0;

    --====== Process the First(root) Key ====    
    SET     @key_depth=1;
    DECLARE @Key as nvarchar(max);    SELECT  @Key=Value FROM  @tbl_Keys WHERE id=@key_depth
    IF  @key_count >= 1
    AND EXISTS( SELECT  * 
                FROM    keyednodes KN  
                WHERE   KN.Value=@Key 
                  and   KN.ParentNode_Id is null
              )
    BEGIN
        DECLARE @ValueExistsForFirstKey as bit; SET     @ValueExistsForFirstKey=0;

        --Load @Key's child nodes
        INSERT INTO @ChildNodes
        SELECT      chld.Id, chld.Value, chld.ParentNode_Id 
        FROM        keyednodes prnt 
        INNER JOIN  keyednodes chld   ON prnt.id=chld.ParentNode_id  
        WHERE       prnt.value=@Key 
          And       prnt.ParentNode_Id is null

        -- Load first @Key's global Variables
        INSERT INTO @Variable  
        SELECT      GV.Value,GV.KeyedNodes_Id,KN.Value,1,GV.ReadOnly 
        FROM        keyednodes KN 
        INNER JOIN GlobalVariables GV 
            on  KN.Id=GV.KeyedNodes_Id 
            and KN.Value=@Key 
            and KN.ParentNode_Id is null

        IF @@ROWCOUNT > 0   
            SET @ValueExistsForFirstKey=1        
        ELSE IF NOT EXISTS(SELECT * FROM @ChildNodes)
        BEGIN
            INSERT INTO @Variable SELECT -1,-1,-1,0,0 --Node exists but No value or children exists
            GOTO ExitFROMProcedure
        END
    END

--====== the tree-traversal Loop ====
    WHILE   @key_depth <= @key_count
    BEGIN
        SET @key_depth=@key_depth+1

        -- get the current Key
        SELECT  @Key=Value FROM  @tbl_Keys WHERE id=@key_depth

         --Does Key path exist?
        IF NOT EXISTS(SELECT * FROM @ChildNodes WHERE Value=@Key)   GOTO ExitFROMProcedure

        SELECT  @ParentId=Id FROM @ChildNodes WHERE Value=@Key
        SET     @KeyIsChild=1

        delete @ChildNodes

        INSERT INTO @ChildNodes
        SELECT      chld.Id, chld.Value, chld.ParentNode_Id 
        FROM        keyednodes prnt 
        INNER JOIN  keyednodes chld   ON prnt.id=chld.ParentNode_id  
        WHERE       chld.ParentNode_Id=@ParentId
    END

--BREAK: jumps to here
    if exists(SELECT * FROM @ChildNodes)
    begin

        INSERT INTO @Variable  
        SELECT      GV.Value, CN.id, CN.Value, 0, GV.ReadOnly 
        FROM        @ChildNodes CN 
        left join   GlobalVariables GV  ON CN.Id=GV.KeyedNodes_Id --children
        union all
        SELECT      GV.Value, GV.KeyedNodes_Id, KN.Value, 1, GV.ReadOnly  
        FROM        keyednodes KN 
        inner join  GlobalVariables GV  ON KN.Id=GV.KeyedNodes_Id and KN.Id=@ParentId--children

        IF  @@ROWCOUNT = 0
        BEGIN
            IF  @key_count <> 1
            OR  NOT EXISTS (SELECT * FROM @Variable)
                INSERT INTO @Variable SELECT -1,-1,-1,0,0
        END

    end
    else
    Begin

        IF @KeyIsChild = 1
        BEGIN
            INSERT INTO @Variable  
            SELECT      GV.Value, GV.KeyedNodes_Id, KN.Value, 1, GV.ReadOnly 
            FROM        keyednodes KN 
            inner join  GlobalVariables GV 
                ON  KN.Id=GV.KeyedNodes_Id 
                and KN.Id=@ParentId --node

            IF @@ROWCOUNT = 0
                    INSERT INTO @Variable SELECT -1,-1,-1,0,0 --Node exists but No value or children exists
        END
    End

 ----
 ExitFromProcedure: --Not Found jumps here

    SELECT      KeyedNodes_Id, KeyNodeValue, Value, IsParent, Isnull(IsReadOnly,0) as IsReadOnly  
    FROM        @Variable 
    ORDER by    IsParent desc, KeyNodeValue asc

    IF(@TurnOnUpdation=1)
    BEGIN
        --update LastAccessDateTime
        UPDATE  GlobalVariables 
        SET     LastAccessDateTime = getdate() 
        WHERE   KeyedNodes_Id IN (SELECT KeyedNodes_Id FROM @Variable)
    END

End

Go

答案 2 :(得分:0)

尝试将隔离级别设置为读取未提交以检查其是否存在阻塞问题

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED