Ошибка извлечения SQL Server при большой нагрузке (200 одновременных пользователей) - PullRequest
2 голосов
/ 07 февраля 2012

В настоящее время я столкнулся с проблемой в процедуре извлечения SQL Server.

Хранимая процедура возвращает неожиданный результат при одновременном выполнении многими пользователями (количество пользователей> 40) для одного и того же параметра.Для Нет пользователей <= 20 показывает ожидаемый результат.Уровень ошибок 0,07-0,10% измеряется с помощью jMeter.</p>

Ожидается, что следующий оператор SQL вернет одну строку.Но при большой нагрузке он иногда не возвращает строк.

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

Здесь хранится proc.

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 

Вот UDF Split (), на который ссылается описанная выше процедура:

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 ]

4 голосов
/ 07 февраля 2012

Это почти наверняка проблема уровня изоляции транзакций. Я не потратил время на то, чтобы по-настоящему разобраться в логике процесса, но, просто бросив быстрый взгляд, кажется, что эта процедура обновляет состояние, которое портится из-за других соединений, вызывая периодические проблемы с данными.

Необходимо попробовать (в вашей базе данных 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

вероятно, ваша пропускная способность снизится, но ваши результаты станут правильными. Тщательно проанализируйте процесс, чтобы найти правильный уровень изоляции транзакции. Возможно, SET TRANSACTION ISOLATION LEVEL READ COMMITTED SNAPSHOT вернет правильные ответы с более высокой пропускной способностью, но это то, что нужно сначала проанализировать и проверить.

3 голосов
/ 07 февраля 2012

Прежде всего, ответ IMO Дэвида Маркла является правильным.Однако FWIW я публикую этот ответ, чтобы предоставить вам очищенный, исправленный (IMHO) пример кода, в котором удалены все подсказки "(nolock)".

Я не реализовал изоляциюи заявления контейнера транзакций, но я могу, если требуется.Вместо этого я обычно рекомендую 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
0 голосов
/ 07 февраля 2012

попробуйте установить уровень изоляции для чтения незафиксированным, чтобы проверить, не является ли проблема блокировки

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...