¿Se me puede notificar automáticamente el bloqueo prolongado en el servidor SQL?


8

Aproximadamente una vez a la semana tengo que resolver una cadena de bloqueo en una base de datos SQL Server 2005, causada por un bloqueo de lectura de larga duración desde un front-end de Access 2003. El bloqueo se elimina cada vez que un usuario abre un formulario determinado y se libera una vez que el usuario ha terminado de desplazarse por el formulario o lo cierra. Dado que muchos de nuestros usuarios abren este formulario como referencia, estos bloqueos permanecen por un tiempo. Cualquier actualización de la tabla provoca el bloqueo, y de repente nadie puede seleccionar de esta tabla, ya que todos están esperando el primer bloqueo. Esto es un gran problema para nosotros, ya que muchas aplicaciones dependen de estos datos. Entiendo que este comportamiento de bloqueo es parte de cómo funciona Access con tablas vinculadas.

He estado resolviendo el problema desde Activity Monitor, eliminando el proceso SELECT que sea el Head Blocker cada vez que me entero de ello. Este es un problema no solo porque me lleva tiempo hacerlo manualmente, sino también porque es reactivo. Para cuando me enteré, ya ha sido un problema para mucha gente.

Me gustaría saber si hay una forma automática de verificar estas cadenas de bloqueo duraderas, y si se envía por correo electrónico o si el problema se resuelve automáticamente. La lógica parece bastante sencilla ("si algún proceso que coincide con esta consulta SELECT ha estado bloqueando durante más de un minuto, notifíqueme / elimínelo") pero no sé cómo implementar esto con SQL Server.

Por lo que vale, creo que la solución adecuada es arreglar o reescribir la aplicación. Sin embargo, debido a la política departamental, esta no es una opción para los próximos meses, por lo que estoy buscando una solución provisional.


Respuestas:


9

¿Has considerado usar el aislamiento de instantáneas ? Habilitar read_committed_snapshot en la base de datos hará que todas las lecturas (selecciones) estén libres de bloqueo:

alter database [...] set read_committed_snapshot on;

No hay cambios en la aplicación. Algunas semánticas cambian bajo la instantánea y su aplicación puede reaccionar de manera extraña, pero esa es la excepción, no la norma. La gran mayoría de las aplicaciones no notan ninguna diferencia, solo obtienen un aumento de rendimiento gratuito.

De todos modos, pensé responder también a la pregunta original : cómo detectar (y posiblemente matar) una consulta de larga duración. En realidad, el motor ya lo hace por ti. Se genera un evento cuando se supera un umbral: Clase de evento de informe de proceso bloqueado . El umbral se configura a través de la Opción de umbral de proceso bloqueado . Cualquier evento de rastreo se puede convertir en una Notificación de evento y las notificaciones de eventos pueden activar los procedimientos . Conecte los puntos y tendrá un código activado a pedido que se ejecutará cuando el motor detecte una consulta que haya cruzado un umbral de tiempo de ejecución. Sin encuestas, sin monitoreo. Tenga en cuenta que la notificación es asincrónica, para cuando la procese, la consulta puede haberse completado, por lo que debe tenerse en cuenta.

Aquí hay un ejemplo:

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 ;

Ahora, en una nueva consulta, configure una WAITFORespera de una notificación:

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

Y sigue adelante y causa un bloqueo. Utilicé un proceso que creó una tabla y no se confirmó, y desde otra ventana de consulta intenté seleccionar de la tabla. En 20 segundos (mi umbral configurado arriba) recibí el informe de bloqueo:

<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>

Dejaré la tarea de concluir esto en un proceso automatizado como ejercicio para el lector. Y sí, el procedimiento de cola / servicio / activado debe estar en [msdb].


No lo he hecho, ¡pero definitivamente voy a leerlo! ¿Qué tipo de rareza debería estar buscando? Si generalmente es un aumento del rendimiento, ¿hay alguna razón por la cual el aislamiento de instantáneas no esté habilitado de forma predeterminada?
Guerrero Bob

persiguiendo el enlace dentro del enlace provisto, lea esto y vea cómo se aplica a su situación
swasheck

3
Recomiendo leer Comparar diferentes resultados con RCSI y lectura comprometida y los enlaces al final. Se justifican preocupaciones especiales si tiene UDF de múltiples declaraciones, por ejemplo. Las lecturas que involucran UDF en READ_COMMITTED_SNAPSHOT pueden parecer inconsistentes . En última instancia, necesitas probar. Pero, de nuevo, la mayoría de los casos no hay efectos visibles.
Remus Rusanu

1
No hay efectos visibles en la aplicación, estoy de acuerdo. En el sistema de base de datos, querrá vigilar tempdb. Hay más carga allí desde read_committed_snapshot.
Grant Fritchey

1
@AlexKuznetsov: la manera muy clara de desplegar RCSI revela su naturaleza: se implementa mediante un solo cambio en la base de datos y asigna silenciosamente lectura confirmada a la instantánea para cada declaración. Todo esto me dice "intento desesperado de arreglar una aplicación rota que no se puede cambiar". El OP actualmente está considerando matar los procesos de bloqueo cada N minutos . Darle a RCSI una prueba de manejo me parece bastante razonable en tal caso. Sé por experiencia que el número de casos que RCSI ayuda y no rompe las cosas supera con creces los casos en que ocurren problemas.
Remus Rusanu

5

Puede crear su propia herramienta de monitoreo o buscar una solución de terceros que pueda proporcionarle una. Si está interesado en crear el suyo, depende de la versión de SQL Server con la que esté trabajando. Si es 2005, puede usar el evento de rastreo Informe de proceso bloqueado . Si está ejecutando 2008 o superior, sugeriría usar el evento extendido equivalente, bloqueado_proceso_informe. Jonathan Kehayias tiene una buena redacción sobre cómo usarlo.

Si está buscando productos de terceros, el Monitor SQL del software Red Gate ha integrado el proceso de bloqueo y las alertas de proceso de larga ejecución.


3

Aunque esto no aborda cómo notificarle el problema, este procedimiento le mostrará cómo consultar para ver si existe un bloqueo. También generará comandos kill para usted, si pasa el parámetro correcto.

Espero que esto te dé algunas ideas.

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

Le estás dando el martillo antes de dejarlo estudiar mejor los problemas de bloqueo :-). Diría que es mejor que cambies la condición para matar solo las sesiones de MSACCESS: D.
Marian

Solo intentaba mostrar cómo comenzar a investigar ... aunque es un proceso antiguo ... probablemente no funcionará en 2012
datagod

2

Sugeriría leer el siguiente tema del foro de MSDN . Se trata del bloqueo causado por el acceso a una base de datos de SQL Server. La sugerencia es principalmente acceder a las tablas mediante consultas utilizando la sugerencia NOLOCK, para que no cause ningún problema de bloqueo. NOLOCK no es la mejor solución, ya que puede causar otros problemas, pero reducirá la mayoría de sus problemas de bloqueo.

La mejor solución sería implementar la idea de Remus, configurar el aislamiento de la instantánea en su base de datos. O implemente el nivel de aislamiento de instantáneas solo para ciertas conexiones que encuentre que causan bloqueo.

Para monitorear adecuadamente su servidor en busca de problemas de bloqueo, sugiero:

  • construir un rastreo del lado del servidor que monitoreará los problemas de bloqueo por más de x segundos (yo diría que 5 es lo suficientemente bueno);
  • guarde las trazas superiores todos los días para tener un historial de al menos los últimos 30 días para ver tendencias y patrones;
  • tenga un trabajo por hora que estudie el archivo de rastreo de los días actuales y le envíe por correo electrónico cualquier situación de bloqueo interesante;

Si desea una respuesta proactiva a este problema, en lugar de tener un trabajo cada hora para monitorear los rastros, haga que se ejecute cada minuto y elimine cualquier sesión de acceso de bloqueo líder.


0

Siguiendo la excelente respuesta de @Remus Rusanu, he realizado la tarea del lector para conectar el evento a un procedimiento almacenado.

En mi caso, el sp escribirá el xml del evento de bloqueo en una tabla, pero eres libre de hacer lo que quieras en esa posición.

Entonces, siga el código de Remus y cree el queue, el servicey el notificationcon una simple copia / pegar desde arriba. Agregue las sp_configureopciones y básicamente está configurado.

Lo único que queda por hacer son

  • Crea un SP sin argumentos.
  • Crear una tabla para que el SP escriba los datos (por ejemplo, su SP puede variar)
  • Active el SP en el queue

Tan pronto como active el SP, los eventos comenzarán a fluir a su mesa.

Descubrí que la cola se desactiva automáticamente si el SP tiene un error. En ese caso, debe ir a Server Studio y activarlo nuevamente en el menú contextual de la entrada de cola ( [msdb]->Service Broker->Warteschlangenen versión alemana).

Me llevó bastante tiempo hacer que esto funcionara y encontrar los lugares correctos en la documentación, por lo que supongo que esto también es útil para otros. Estoy usando SQLServer 2005.

Crea el SP sin 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

Crear la pdix_lock_eventstabla

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

Active el SP en el queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.