Posso essere avvisato automaticamente del blocco prolungato nel server SQL?


8

Circa una volta alla settimana devo risolvere una catena di blocco su un database SQL Server 2005, causata da un blocco di lettura di lunga durata da un front-end di Access 2003. Il blocco viene rimosso ogni volta che un utente apre un determinato modulo e viene rilasciato una volta che l'utente ha finito di scorrere il modulo o lo chiude. Poiché molti dei nostri utenti aprono questo modulo come riferimento, questi blocchi rimangono attivi per un po '. Qualsiasi aggiornamento alla tabella provoca il blocco e all'improvviso nessuno può selezionare da questa tabella poiché sono tutti in attesa del primo blocco. Questo è piuttosto un problema per noi, dal momento che molte app fanno affidamento su questi dati. Comprendo che questo comportamento di blocco fa parte del modo in cui Access funziona con le tabelle collegate.

Ho risolto il problema da Activity Monitor, uccidendo qualsiasi processo SELECT sia Head Blocker ogni volta che lo scopro. Questo è un problema non solo perché mi ci vuole tempo per farlo manualmente, ma anche perché è reattivo. Quando ne ho sentito parlare, è già stato un problema per molte persone.

Mi piacerebbe sapere se esiste un modo automatico per verificare la presenza di queste catene di blocco di lunga durata e ricevere e-mail o risolvere automaticamente il problema. La logica sembra abbastanza semplice ("se un processo che corrisponde a questa query SELECT è stato bloccato per più di un minuto, avvisami / uccidilo") ma non so come implementarlo con SQL Server.

Per quello che vale, penso che la soluzione corretta sia riparare o riscrivere l'app. Tuttavia, a causa della politica dipartimentale, questa non è un'opzione per i prossimi mesi, quindi sto cercando una pausa.


Risposte:


9

Hai preso in considerazione l'utilizzo dell'isolamento dell'istantanea ? Abilitando read_committed_snapshot nel database, tutte le letture (seleziona) saranno libere da blocco:

alter database [...] set read_committed_snapshot on;

Nessuna modifica dell'applicazione. Alcune semantiche cambiano sotto un'istantanea e l'applicazione potrebbe reagire in modo strano, ma questa è l'eccezione non la norma. La stragrande maggioranza delle applicazioni non nota alcuna differenza, ma ottiene solo un aumento delle prestazioni gratuito.

Comunque, ho pensato di rispondere anche alla domanda originale : come rilevare (e possibilmente uccidere) una query di lunga durata. In realtà, il motore lo fa già per te. Si è verificato un evento quando viene superata una soglia: Classe evento report processo bloccato . La soglia viene configurata tramite l' opzione della soglia di processo bloccata . Qualsiasi evento di traccia può essere trasformato in una notifica di eventi e le notifiche di eventi possono attivare procedure . Collega i punti e disponi di un codice attivato su richiesta che viene eseguito quando il motore rileva una query che ha superato un limite di tempo di esecuzione. Nessun polling, nessun monitoraggio. Si noti tuttavia che la notifica è asincrona, al momento dell'elaborazione, la query potrebbe essere stata completata in modo da tenerne conto.

Ecco un esempio:

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 ;

Ora, in una nuova query, imposta WAITFORuna notifica in attesa:

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

E vai avanti e causa qualche blocco. Ho usato un processo che ha creato una tabella e non ha eseguito il commit, e da un'altra finestra di query ho cercato di selezionare dalla tabella. In 20 secondi (la mia soglia configurata sopra) ho ricevuto il rapporto di blocco:

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

Lascerò il compito di racchiuderlo in un processo automatizzato come esercizio per il lettore. E sì, la coda / servizio / procedura attivata deve essere in [msdb].


Non l'ho fatto, ma sicuramente lo leggerò! Che tipo di stranezza dovrei cercare? Se si tratta generalmente di un aumento delle prestazioni, c'è un motivo per cui l'isolamento dello snapshot non è abilitato per impostazione predefinita?
Warrior Bob

inseguendo il link all'interno del link fornito, leggere questo e vedere come si applica alla tua situazione
swasheck

3
Consiglio di leggere Confronto di risultati diversi con RCSI e Read Commit e i collegamenti alla fine. Preoccupazioni speciali sono garantite se si dispone di UDF multi-dichiarazione, ad es. Le letture che coinvolgono UDF in READ_COMMITTED_SNAPSHOT possono sembrare incoerenti . Alla fine devi testare. Ma ancora una volta, la maggior parte dei casi non ci sono effetti visibili.
Remus Rusanu,

1
Nessun effetto visibile sull'app, sono d'accordo. Nel sistema di database ti consigliamo di tenere d'occhio tempdb. C'è più carico lì da read_committed_snapshot.
Concedi Fritchey il

1
@AlexKuznetsov: il modo in cui viene distribuito RCSI rivela la sua natura: viene distribuito da una singola modifica a DB e mappa silenziosamente il commit della lettura per lo snapshot per ogni istruzione. Tutto ciò mi dice "un disperato tentativo di riparare un'app rotta che non può essere modificata". L'OP sta attualmente valutando la possibilità di eliminare i processi di blocco ogni N minuti . Dare a RCSI un test drive mi sembra abbastanza ragionevole in questo caso. So per esperienza che il numero di casi che RCSI aiuta e non rompe le cose supera di gran lunga i casi in cui si verificano problemi.
Remus Rusanu,

5

Puoi creare il tuo strumento di monitoraggio o cercare una soluzione di terze parti in grado di fornirti una soluzione. Se sei interessato a crearne uno tuo, dipende da quale versione di SQL Server stai lavorando. Se è il 2005, è possibile utilizzare l' evento di traccia Rapporto processo bloccato . Se stai eseguendo il 2008 o versioni successive, ti suggerirei di utilizzare l'evento esteso equivalente, block_process_report. Jonathan Kehayias ha una buona scrittura su come usarlo.

Se stai cercando prodotti di terze parti, SQL Monitor del software Red Gate ha bloccato gli avvisi di processo e di processo a lungo termine integrati.


3

Sebbene ciò non risolva il modo in cui notificare il problema, questa procedura ti mostrerà come eseguire una query per vedere se esiste un blocco. Genererà anche comandi kill per te, se passi il parametro corretto.

Spero che questo ti dia alcune idee.

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

Gli stai dando il martello prima di permettergli di studiare meglio i problemi di blocco :-). Direi che è meglio cambiare la condizione per uccidere solo le sessioni MSACCESS: D.
Marian,

Stavo solo cercando di mostrare come iniziare a indagare ... è un vecchio proc però ... probabilmente non funzionerà nel 2012
datagod

2

Suggerirei di leggere il seguente argomento del forum MSDN . Si tratta del blocco causato dall'accesso a un db di SQL Server. Il suggerimento è principalmente di accedere alle tabelle tramite query utilizzando l'hint NOLOCK, in modo che non causino alcun problema di blocco. NOLOCK non è la soluzione migliore, in quanto può causare altri problemi, ma ridurrà la maggior parte dei problemi di blocco.

La soluzione migliore sarebbe implementare l'idea di Remus, impostare l'isolamento dello snapshot sul database. Oppure implementa il livello di isolamento dello snapshot solo per determinate connessioni che ritieni possano causare il blocco.

Al fine di monitorare correttamente il server per problemi di blocco, suggerirei:

  • costruire una traccia lato server che monitorerà i problemi di blocco per più di x secondi (direi che 5 è abbastanza buono);
  • salva le tracce superiori ogni giorno in modo da avere una cronologia degli ultimi 30 giorni per vedere tendenze e modelli;
  • svolgere un lavoro orario che studi il file di traccia dei giorni attuali e ti invii via email eventuali situazioni di blocco interessanti;

Se si desidera una risposta proattiva a questo problema, invece di avere un lavoro ogni ora per monitorare le tracce, farlo funzionare ogni minuto e terminare qualsiasi sessione di accesso di blocco principale.


0

Seguendo l'eccellente risposta di @Remus Rusanu, ho svolto il compito del lettore di collegare l'evento a una procedura memorizzata.

Nel mio caso lo sp scriverà l'xml dell'evento di blocco su una tabella, ma sei libero di fare quello che vuoi in quella posizione.

Quindi, segui il codice di Remus e crea il queue, il servicee il notificationcon una semplice copia / incolla dall'alto. Aggiungi le sp_configureopzioni e sei praticamente impostato.

L'unica cosa che resta da fare sono

  • Crea un SP senza argomenti.
  • Crea una tabella per la SP in cui scrivere i dati (per il mio esempio, la tua SP può variare)
  • Attiva il SP sul queue

Non appena avrai attivato l'SP, gli eventi inizieranno a fluire nel tuo tavolo.

Ho scoperto che la coda si disattiva immediatamente, se SP ha un errore. In tal caso, devi andare su Server Studio e riattivarlo nel menu di scelta rapida della voce della coda ( [msdb]->Service Broker->Warteschlangennella versione tedesca).

Mi ci è voluto un po 'di tempo per farlo funzionare e trovare i punti giusti nella documentazione, quindi suppongo che questo sia utile anche per gli altri. Sto usando SQL Server 2005.

Crea l'SP senza argomenti

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

Crea la pdix_lock_eventstabella

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

Attiva il SP sul queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.