Posso ser notificado automaticamente sobre o bloqueio prolongado no servidor SQL?


8

Uma vez por semana, tenho que resolver uma cadeia de bloqueio em um banco de dados do SQL Server 2005, causada por um bloqueio de leitura de longa duração de um front-end do Access 2003. O bloqueio é retirado sempre que um usuário abre um determinado formulário e é liberado quando o usuário termina de rolar o formulário ou fechá-lo. Como muitos de nossos usuários abrem este formulário como referência, esses bloqueios ficam por um tempo. Qualquer atualização na tabela causa o bloqueio e, de repente, ninguém pode selecionar nessa tabela, pois todos aguardam o primeiro bloqueio. Isso é um problema para nós, pois muitos aplicativos dependem desses dados. Entendo que esse comportamento de bloqueio faz parte de como o Access funciona com tabelas vinculadas.

Resolvi o problema no Activity Monitor, matando qualquer processo SELECT que seja o Head Blocker sempre que eu descobrir. Esse é um problema não apenas porque levo tempo para fazê-lo manualmente, mas também porque é reativo. No momento em que soube disso, já havia sido um problema para muitas pessoas.

Gostaria de saber se existe uma maneira automática de verificar essas cadeias de bloqueio duradouras e receber um email ou resolver o problema automaticamente. A lógica parece bastante direta ("se algum processo correspondente a essa consulta SELECT estiver bloqueando por mais de um minuto, notifique-me / mate-a"), mas não sei como implementar isso com o SQL Server.

Pelo que vale a pena, acho que a solução adequada é consertar ou reescrever o aplicativo. No entanto, devido à política departamental, essa não é uma opção para os próximos meses, por isso estou procurando um paliativo.


Respostas:


9

Você já pensou em usar o isolamento de instantâneo ? A ativação de read_committed_snapshot no banco de dados fará com que todas as leituras (seleções) fiquem livres de bloqueios:

alter database [...] set read_committed_snapshot on;

Nenhuma alteração de aplicativo. Algumas semânticas mudam no snapshot e seu aplicativo pode reagir de maneira estranha, mas essa é a exceção, não a norma. A grande maioria dos aplicativos não percebe diferença, apenas recebe um aumento de desempenho gratuito.

Enfim, gostaria de responder também à pergunta original : como detectar (e possivelmente matar) uma consulta de longa duração. Na verdade, o mecanismo já faz isso por você. Há um evento gerado quando um limite é passado: Classe de Eventos do Relatório de Processo Bloqueado . O limite é configurado através da opção de limite do processo bloqueado . Qualquer evento de rastreamento pode ser transformado em uma Notificação de Eventos e as notificações de eventos podem ativar procedimentos . Conecte os pontos e você terá um código ativado sob demanda que será executado quando o mecanismo detectar uma consulta que ultrapassou um limite de tempo de execução. Sem pesquisa, sem monitoramento. Observe que a notificação é assíncrona, no momento em que você a processa, a consulta pode ter sido concluída, e isso deve ser levado em consideração.

Aqui está um exemplo:

use msdb;
go

create queue blocked_process_report_queue;
go

create service blocked_process_report_service
    on queue blocked_process_report_queue
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);

create event notification blocked_process_report_notification
    on server
    for BLOCKED_PROCESS_REPORT
    to service N'blocked_process_report_service',
          N'current database';
go  

sp_configure 'show advanced options', 1 ;
GO
RECONFIGURE ;
GO
sp_configure 'blocked process threshold', 20 ;
GO
RECONFIGURE ;

Agora, em uma nova consulta, configure a WAITFORexpectativa de uma notificação:

use msdb;
waitfor(
   receive cast(message_body as xml), * 
   from  blocked_process_report_queue);

E vá em frente e cause algum bloqueio. Eu usei um processo que criou uma tabela e não foi confirmado e, em outras janelas de consulta, tentei selecionar na tabela. Em 20 segundos (meu limite configurado acima), recebi o relatório de bloqueio:

<blocked-process-report>
  <blocked-process>
    <process id="process1b5a24ca8" ...>
      <executionStack>
        <frame line="1" stmtstart="-1"... />
      </executionStack>
      <inputbuf>
          select * from t   </inputbuf>
    </process>
  </blocked-process>
  <blocking-process>
    <process status="sleeping" spid="51" ...>
      <executionStack />
      <inputbuf>
         begin transaction
         create table t (a int)   </inputbuf>
    </process>
  </blocking-process>
</blocked-process-report>

Vou deixar a tarefa de agrupar isso em um processo automatizado como um exercício para o leitor. E sim, o serviço de processo de fila / / activado deve estar em [msdb].


Eu não tenho, mas eu definitivamente vou ler sobre isso! Que tipo de estranheza eu deveria estar procurando? Se geralmente é um aumento de desempenho, existe um motivo pelo qual o isolamento de instantâneo não está ativado por padrão?
Warrior Bob

perseguindo o link dentro do link fornecido, leia este e ver como ele se aplica à sua situação
swasheck

3
Eu recomendo ler Comparando resultados diferentes com RCSI e leitura confirmada e os links no final. Preocupações especiais são garantidas se você tiver UDFs com várias instruções, por exemplo. Leituras envolvendo UDFs em READ_COMMITTED_SNAPSHOT podem parecer inconsistentes . Em última análise, você precisa testar. Mas, novamente, na maioria dos casos, não há efeitos visíveis.
Remus Rusanu

11
Não há efeitos visíveis no aplicativo, eu concordo. No sistema de banco de dados, você deve ficar de olho no tempdb. Existe mais carga no read_committed_snapshot.
22812 Grant Fritchey

11
@AlexKuznetsov: A maneira como o RCSI é implantado revela sua natureza: é implantado por uma única alteração no banco de dados e mapeia silenciosamente o comprometimento da leitura no snapshot para cada instrução. Tudo isso me diz 'tentativa desesperada de corrigir um aplicativo quebrado que não pode ser alterado'. Atualmente, o OP está considerando matar processos de bloqueio a cada N minutos . Dar a RCSI um test drive me parece bastante razoável nesse caso. Sei por experiência própria que o número de casos que o RCSI ajuda e não quebra as coisas supera em muito os casos em que ocorrem problemas.
Remus Rusanu

5

Você pode criar sua própria ferramenta de monitoramento ou procurar uma solução de terceiros que possa fornecer uma para você. Se você estiver interessado em criar seu próprio, isso depende de qual versão do SQL Server você está trabalhando. Se for 2005, você poderá usar o evento de rastreamento Blocked Process Report . Se você estiver executando 2008 ou acima, sugiro usar o evento estendido equivalente, locked_process_report. Jonathan Kehayias tem um bom artigo sobre como usá-lo.

Se você estiver procurando produtos de terceiros, o SQL Monitor do software Red Gate bloqueou os alertas de processo e de longa execução incorporados.


3

Embora isso não resolva como notificá-lo sobre o problema, este procedimento mostrará como consultar para ver se existe algum bloqueio. Também irá gerar comandos de matar para você, se você passar o parâmetro correto.

Espero que isso te dê algumas ideias.

IF (object_id('Who') IS NOT NULL)
BEGIN
  print 'Dropping procedure: Who'
  drop procedure Who
END
print 'Creating procedure: Who'
GO
CREATE PROCEDURE Who
(
  @db varchar(100) = '%',
  @kill char(1) = 'N',
  @tran char(1) = 'N'
)
as

/*  This proc should be rewritten to take advantage of:
  select * from sys.dm_exec_connections
  select * from sys.dm_exec_sessions
  select * from sys.dm_exec_requests
*/



---------------------------------------------------------------------------------------------------
-- Date Created: July 17, 2007
-- Author:       Bill McEvoy
-- Description:  This procedure gives a summary report and a detailed report of the logged-in
--               processes.  This procedure is a derivative of sp_who3 which I wrote in 2002.
--               
---------------------------------------------------------------------------------------------------
-- Date Revised: 
-- Author:       
-- Reason:       
---------------------------------------------------------------------------------------------------
set nocount on

---------------------------------------------------------------------
-- Validate input parameters                                       --
---------------------------------------------------------------------


---------------------------------------------------------------------
-- M A I N   P R O C E S S I N G                                   --
---------------------------------------------------------------------
--                                                                 --
-- Generate login summary report                                   --
--                                                                 --
--                                                                 --
---------------------------------------------------------------------


---------------------------------------------------------------------
-- Generate login summary report                                   --
---------------------------------------------------------------------

select 'loginame'   = convert(char(30),loginame),
       'connection' = count(*),
       'phys_io'    = str(sum(physical_io),10),
--       'cpu'        = sum(cpu),
       'cpu(mm:ss)' = str((sum(cpu)/1000/60),12) + ':' + case 
                                            when left((str(((sum(cpu)/1000) % 60),2)),1) = ' ' then stuff(str(((sum(cpu)/1000) %60),2),1,1,'0') 
                                            else str(((sum(cpu)/1000) %60),2)
                                         end,
       'wait_time'  = str(sum(waittime),12),
       'Total Memory(MB)' = convert(decimal(12,2),sum(convert(float,memusage) * 8192.0 / 1024.0 / 1024.0))
from master.dbo.sysprocesses
where db_name(dbid) like @db
  and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
group by loginame
order by 3 desc



---------------------------------------------------------------------
-- Generate detailed activity report                               --
---------------------------------------------------------------------

select 'loginame'     = left(loginame, 30),
       'hostname'     = left(hostname,25),
       'database'     = left(db_name(dbid),25),
       'spid'         = str(spid,4,0),
       'block'        = str(blocked,5,0),
       'phys_io'      = str(physical_io,10,0),
       'cpu(mm:ss)'   = str((cpu/1000/60),10) + ':' + case when left((str(((cpu/1000) % 60),2)),1) = ' ' then stuff(str(((cpu/1000) % 60),2),1,1,'0') else str(((cpu/1000) % 60),2) END,
       'mem(MB)'      = str((convert(float,memusage) * 8192.0 / 1024.0 / 1024.0),8,2),
       'program_name' = left(program_name,50),
       'command'      = cmd,
       'lastwaittype' = left(lastwaittype,20),
       'login_time'   = convert(char(19),login_time,120),
       'last_batch'   = convert(char(19),last_batch,120),
       'status'       = left(nt_username,20)
  from master..sysprocesses
where db_name(dbid) like @db
  and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
order by 5,4


---------------------------------------------------------------------
-- Generate KILL commands                                          --
---------------------------------------------------------------------

IF (upper(@Kill) = 'Y')
BEGIN
  select 'kill' + ' ' + str(spid,4,0)
    from master..sysprocesses
  where db_name(dbid) like @db
    and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
  order by spid
END



---------------------------------------------------------------------
-- Report on open transactions                                     --
---------------------------------------------------------------------

IF (UPPER(@Tran) = 'Y')
BEGIN

  -- Create the temporary table to accept the results.
  IF (object_id('tempdb..#Transactions') is NOT NULL)
    DROP TABLE #Transactions
  CREATE TABLE #Transactions
  (
    DatabaseName    varchar(30),
    TransactionName varchar(25),
    Details         sql_variant 
  )

  -- Execute the command, putting the results in the table.
  exec sp_msforeachdb '
  INSERT INTO #Transactions (TransactionName, Details)
     EXEC (''DBCC OPENTRAN([?]) WITH TABLERESULTS, NO_INFOMSGS'');
  update #Transactions 
     set DatabaseName = ''[?]''
   where DatabaseName is NULL'

  -- Display the results.
  SELECT * FROM #Transactions order by transactionname
END


go
IF (object_id('Who') IS NOT NULL)
  print 'Procedure created'
ELSE
  print 'Procedure NOT created'
GO


exec who @tran=Y

Você está dando a ele o martelo antes de deixá-lo estudar melhor os problemas de bloqueio :-). Eu diria que é melhor alterar a condição para matar apenas as sessões do MSACCESS: D.
Marian

Eu estava apenas tentando mostrar como começar a investigar ... é um proc antiga embora ... provavelmente não vai funcionar em 2012
datagod

2

Eu sugiro ler o seguinte tópico do fórum do MSDN . Trata-se de bloqueio causado pelo acesso a um banco de dados do SQL Server. A sugestão é principalmente acessar as tabelas através de consultas usando a dica NOLOCK, para que isso não cause nenhum problema de bloqueio. O NOLOCK não é a melhor solução, pois pode causar outros problemas, mas reduzirá a maioria dos problemas de bloqueio.

A melhor solução seria implementar a idéia de Remus, configurar o isolamento de instantâneo em seu banco de dados. Ou implemente o nível de isolamento de captura instantânea apenas para determinadas conexões que estão causando bloqueio.

Para monitorar adequadamente seu servidor quanto a problemas de bloqueio, sugiro:

  • criar um rastreamento do lado do servidor que monitore os problemas de bloqueio por mais de x segundos (eu diria que 5 é bom o suficiente);
  • salve os traços superiores todos os dias para ter um histórico de pelo menos os últimos 30 dias para ver tendências e padrões;
  • tenha uma tarefa horária que estude o arquivo de rastreamento dos dias atuais e envie por e-mail todas as situações de bloqueio interessantes;

Se você deseja uma resposta proativa a esse problema, em vez de ter um trabalho a cada hora para monitorar os rastreamentos, execute-o a cada minuto e elimine qualquer sessão principal do Access de bloqueio.


0

Após a excelente resposta de @Remus Rusanu, realizei a tarefa do leitor de conectar o evento a um procedimento armazenado.

No meu caso, o sp gravará o xml do evento de bloqueio em uma tabela, mas você é livre para fazer o que quiser nessa posição.

Então, siga o código do Remus e crie o queue, the servicee o notificationcom uma simples cópia / colar de cima. Adicione as sp_configureopções e você está basicamente definido.

A única coisa que resta a fazer é

  • Crie um SP sem argumentos.
  • Crie uma tabela para o SP para gravar os dados (por exemplo, seu SP pode variar)
  • Ative o SP no queue

Assim que você ativar o SP, os eventos começarão a fluir para a sua mesa.

Eu descobri que a fila se desativa imediatamente, se o SP tiver um erro. Nesse caso, você precisa acessar o Server Studio e ativá-lo novamente no menu de contexto da entrada da fila ( [msdb]->Service Broker->Warteschlangenna versão em alemão).

Levei algum tempo para que isso funcionasse e para encontrar os pontos certos na documentação, então acho que isso também é útil para outras pessoas. Estou usando o SQLServer 2005.

Crie o SP sem argumentos

CREATE PROCEDURE sp_addrecord
AS
  DECLARE @RecvReqDlgHandle UNIQUEIDENTIFIER;
  DECLARE @RecvReqMsg XML;
  DECLARE @RecvReqMsgName sysname;

  WHILE (1=1)
  BEGIN

    BEGIN TRANSACTION;

    WAITFOR
    ( RECEIVE TOP(1)
        @RecvReqDlgHandle = conversation_handle,
        @RecvReqMsg = message_body,
        @RecvReqMsgName = message_type_name
      FROM blocked_process_report_queue
    ), TIMEOUT 5000;

    IF (@@ROWCOUNT = 0)
    BEGIN
      ROLLBACK TRANSACTION;
      BREAK;
    END

    IF @RecvReqMsgName = 
        N'http://schemas.microsoft.com/SQL/Notifications/EventNotification'
    BEGIN
        print 'Insert SQL to pdix_lock_events'
        INSERT INTO pdix_lock_events
               VALUES(null, cast( @RecvReqMsg as xml));

    END
    ELSE IF @RecvReqMsgName =
        N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    BEGIN
       END CONVERSATION @RecvReqDlgHandle;
    END
    ELSE IF @RecvReqMsgName =
        N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
    BEGIN
       END CONVERSATION @RecvReqDlgHandle;
    END

    COMMIT TRANSACTION;

  END
GO

Crie a pdix_lock_eventstabela

USE [msdb]
GO

/****** Object:  Table [dbo].[pdix_lock_events]    Script Date: 05/06/2015 17:48:36 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[pdix_lock_events](
    [locktime] [timestamp] NULL,
    [lockevent] [xml] NOT NULL
) ON [PRIMARY]

GO

Ative o SP no queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.